滥用 HTTP hop-by-hop 请求头
在这篇文章中,我将介绍可用于通过滥用 HTTP/1.1 hop-by-hop 标头以意想不到的方式影响 Web 系统和应用程序的技术。受这些技术影响的系统很可能在到达后端应用程序之前具有多个缓存/代理处理请求。
什么是逐跳报头?
hop-by-hop header 是一个 header,它被设计为由当前处理请求的代理处理和使用,而不是一个 end-to-end header,它被设计为一直存在于请求中到请求结束。根据RFC 2612
中,HTTP / 1.1规范治疗以下标题为逐跳默认为:Keep-Alive
,Transfer-Encoding
,TE
,Connection
,Trailer
,Upgrade
,Proxy-Authorization
和Proxy-Authenticate
。当在请求中遇到这些标头时,兼容代理应该处理或操作这些标头所指示的任何内容,而不是将它们转发到下一跳。
除了这些默认值之外,请求还可以定义一组自定义的标头,
通过将它们添加到Connection
标头来逐跳处理
,如下所示:
Connection: close, X-Foo, X-Bar
在此示例中,我们要求代理将X-Foo
和X-Bar
视为逐跳处理,这意味着我们希望代理在传递请求之前将它们从请求中删除。这种 HTTP/1.1 定义自定义逐跳标头的能力是本文中技术和发现的关键。
滥用逐跳报头的理论
从 HTTP 请求中删除标头的行为不一定会导致问题,但是能够删除不在原始请求中但由前端或链中的另一个代理添加的标头可能会产生不可预测的结果。这基本上就像关闭一个开关——它可能什么都不做,或者可能是灾难性的。
例如,可能在链中的某处添加了一个标头,它指示后端访问控制决策或请求来自互联网用户的事实,并且它在请求中的缺失会触发应用程序中的逻辑更改。也许应用程序逻辑假设标头将存在,因为代理无条件地添加标头,并在它不存在时转储出多汁的调试错误信息。前端代理转发逐跳报头列表的行为可能会产生问题,因为它添加到请求中的任何报头都可能被下一跳删除,并且每次链中的一跳转发逐跳列表而不是消耗标头,增加了影响的机会。
您可能已经注意到,Connection
标头本身作为默认的逐跳标头在上面列出。这表明合规代理在转发请求时不应将请求的自定义逐跳标头列表转发到其Connection
标头中链中的下一个服务器- 也就是说,合规代理应Connection
完全使用请求的标头. 但是,我的研究表明,这可能并不总是按预期发生——某些系统似乎也转发整个Connection
报头,或者复制逐跳列表并将其附加到自己的Connection
报头。例如,HAProxy 似乎可以Connection
原封不动地传递标头,就像 Nginx 在充当代理时一样。
下图显示了如果后端期望X-Important-Header
并将其存在纳入逻辑决策中,滥用逐跳标头可能会如何产生问题:
我将介绍一些滥用逐跳标头对我在下面遇到的 Web 应用程序造成影响的示例,以及有关可以在何处找到影响的一些想法,但潜在的结果将非常特定于应用程序和目标基础设施,目标标头以及它们对后端的意义。
测试逐跳报头滥用的可能性
在我们进入现实世界的例子之前,在深入研究之前,了解系统是否容易受到某种逐跳标头滥用可能会很方便。幸运的是,这很快就可以测试 - 确定一个请求标头,当它存在和不存在时,它会在响应中产生明显的差异,然后看看当它作为逐跳标头添加时会发生什么。如果系统正在删除标头,那么当标头同时出现在请求中并且在Connection
标头中列出时,响应应该与它根本不存在于请求中时相同,但与出现在请求中时不同请求而不是作为逐跳标头列出。
一个快速而简单的测试是Cookie
针对需要身份验证的端点(假设目标系统使用 cookie 身份验证)的标头。以下面的请求为例:
GET /api/me HTTP/1.1
Host: foo.bar
Cookie: session=xxx
Connection: close, Cookie
如果我们说它/api/me
应该在请求通过身份验证时返回带有用户详细信息的 HTTP 200,并且session=xxx
是一个有效的经过身份验证的 cookie 会话值,那么如果系统允许跳转,则上述请求可能会返回预期响应以外的内容 -原始请求中定义的 hop 标头,以修改将哪些标头发送到后端。
在这个例子中,Cookie
标头是在原始请求中提供的,所以代理在发送请求之前通过删除它没有做任何错误,因此这个测试只是一个非常基本的指示,代理(前端或另一个在链中)尊重我们的自定义逐跳标头列表并对其进行删除 - 它不确认我们的自定义逐跳列表正在沿链转发到另一个代理,这是事情变得更有趣的地方(从现在开始,我将称之为“逐跳转发”)。要对此进行测试,您可能需要借助诸如 Burp 的 Intruder 之类的工具或我编写的以下脚本(它还测试缓存中毒,如下所述):
https://gist.github.com/ndavison/298d11b3a77b97c908d63a345d3c624d
如果您传入已知标头列表,例如this one ,您可以观察哪些标头会造成影响,尽管它们不在您的原始请求中:
for HEADER in $(cat headers.txt); do python poison-test.py -u "https://target" -x "$HEADER"; sleep 1; done
这将循环遍历整个标题列表并打印出它是否在逐跳列表中的存在创建了不同的状态代码或响应正文大小。如果它的存在导致了不同的响应并且标头不在您的原始请求中(在我的脚本中,很少有),您可能发现了一个值得探索的问题,因为这表明正在转发逐跳列表至少一跳。
因此,现在涵盖了一般理论和测试,让我们进入一些该技术可能有用的用例。
通过隐藏 X-Forwarded-For 来屏蔽原始 IP 地址
当前端代理接受用户请求时,它可能会将此用户的 IP 地址添加到X-Forwarded-For
(XFF) 标头中,因此后端的基础设施和应用程序可以知道请求用户的 IP 地址。但是,通过指示代理此标头是逐跳的,我们最终可能会从请求中删除此标头,后端应用程序将永远不会收到它,或者它将收到一个不是原始 IP 地址值用户,但属于链中其他位置的服务器。
例如,在 CloudFoundry中的 gorouter 将请求
转发到后端应用程序之前,如果请求中尚不存在标头
,则会将其之前设备的 IP 地址设置为 XFF 标头值
。因此,如果 gorouter 之前的设备转发请求的逐跳列表并且此列表包含X-Forwarded-For
,则 gorouter 将删除,X-Forwarded-For
然后将其设置X-Forwarded-For
为先前设备的 IP 地址并将其转发到后端应用程序,从而有效地清理标头原始请求者的 IP 地址,至少就后端应用程序而言是这样。
除了从系统基础设施的某些组件伪装原始 IP 之外,该技术还可以提供一种影响身份验证或访问控制决策的方法。想象一下负载均衡器后面的应用程序,然后转发到代理,最终转发到应用程序。当遇到来自本地 IP 范围(例如10.1.2.0/24
)的请求时,此应用程序会以某种方式将请求视为受信任的,可能会授予对/admin
. 因为它在一个可靠的代理之后,应用程序可能相信,即使攻击者尝试传统的X-Forwarded-For
欺骗,负载均衡器仍然会将真实的原始 IP 附加到标头,这样看起来像<attacker spoofed ip>, <real attacker ip>
,因此该应用程序可以安全地处理欺骗尝试。但是,如果 XFF 标头在到达应用程序之前被剥离,如果攻击者将 XFF 添加为逐跳标头,则可能是这种情况,那么代理(如 gorouter)将X-Forwarded-For
通过采取负载均衡器之前的 IP 地址作为请求 IP(例如10.1.2.3
),X-Forwarded-For
到达应用程序的最终值将是10.1.2.3
,不附加任何其他内容。在这样的应用程序中,此请求将授予访问权限,/admin
因为这是一个本地地址。
要记住的另一件事是XFF只有一个用于传递用户的真实IP地址头-根据不同的系统上有针对性的,你也可以有Forwarded
,X-Real-IP
和一堆别人认为是不太常见的。
指纹服务
使用该技术来查找上述测试说明中概述的转发的逐跳标头,人们可能会根据标头收集有关系统的更多信息,当由于此技术而从请求中删除这些标头时,会导致错误或否则有明显差异。显然,标头对特定技术或堆栈越具体,让它触发这种结果就越有说服力——例如,消除X-Forwarded-Proto
导致问题的信息可能不如类似的东西那样提供信息X-BLUECOAT-VIA
。
如果前端本身由于不喜欢您尝试添加列入黑名单的逐跳而出现错误(您可以根据响应返回给您的速度得出结论,相对于目标系统的响应)其他错误,缓存命中等的时间),那么这本身可能很有用 - 无论错误是由系统故障产生还是因为前端系统仅针对此技术进行了加固,对于信息收集的目的来说并不重要. 例如,当设置标头一样Age
,Host
,X-Forwarded-For
,Server
和公平一些其他常见的作为逐跳返回一个非常简单的0体长HTTP/1.1 400 Bad Request
从清漆,其可以是用于检测这样的高速缓冲存储器,如果它是否则不倾翻的另一种方式其存在于响应头等中。
缓存中毒 DoS
这更多的是理论而不是实践,因为在针对漏洞赏金计划范围内的系统进行测试时,我还没有遇到过真实世界的例子,但这里的影响与缓存中毒 DoS 研究 和负责任的拒绝中 涵盖的结果非常相似PortSwigger 上的Web 缓存中毒研究的服务 ,但是该技术略有不同 - 我们不是直接使用或修改请求标头,这些请求头会创建有害的应用程序状态,从而使 Web 缓存中毒,而是滥用逐跳标头来创建不需要的应用程序状态,通过删除应用程序正常运行所依赖的标头,在这种情况下,是由代理添加到请求中的标头。
为了使其可被利用,我们需要做的是:系统的前端缓存转发逐跳标头列表而不是使用它,中间代理处理逐跳列表并删除标头或者它,链中更远的另一个代理,或后端应用程序需要,并且删除此类标头的行为会导致不受欢迎的响应,例如在 Web 缓存之后由某些内容返回的 HTTP 400 或 501。就像上面的研究一样,这可能会导致应用程序前面的 Web 缓存选择接受这种不受欢迎的响应作为服务其他用户的副本,因此我们有缓存中毒拒绝服务。
虽然我还没有找到使用逐跳报头滥用的缓存中毒 DoS 的真实世界实例,但我遇到了允许逐跳转发的 Varnish 配置。默认情况下,Varnish 似乎Connection
在请求中使用标头并且不会将其添加到后端请求中,但是官方 repo 建议使用以下配置来支持 Websocket:
sub vcl_recv {
if (req.http.upgrade ~ "(?i)websocket") {
return (pipe);
}
}
sub vcl_pipe {
if (req.http.upgrade) {
set bereq.http.upgrade = req.http.upgrade;
set bereq.http.connection = req.http.connection;
}
}
这个配置所做的是将任何Upgrade: websocket
请求发送到vcl_pipe
子程序中,根据官方文档,这意味着“Varnish 停止检查每个请求,只是将字节洗牌到后端”。一旦请求位于管道例程中,此配置将把请求的Connection
标头反映到后端请求的Connection
标头中,这意味着我们已经实现了到后端的逐跳转发,尽管Upgrade: websocket
请求中还有标头。但是,我们可以发送类似的内容Upgrade: websocketz
来尝试阻止后端实际将连接视为 websocket,后端可能会认为该连接无效并被忽略,但会传递if (req.http.upgrade ~ "(?i)websocket")
上述配置中的条件并触发管道行为。
以下请求已使用上述 Websocket 配置针对 Varnish 6.3 进行了测试:
GET / HTTP/1.1
Host: foo.bar
Upgrade: websocketz
Connection: keep-alive, xxx
这是 Varnish 发送到后端的内容:
GET / HTTP/1.1
Host: foo.bar
X-Forwarded-For: 192.168.176.1
xxx: yyy
X-Varnish: 11
upgrade: websocketz
connection: keep-alive, xxx
这是相关的原始清漆日志:
24 Begin b bereq 23 pipe
24 BereqMethod b GET
24 BereqURL b /
24 BereqProtocol b HTTP/1.1
24 BereqHeader b Host: foo.bar
24 BereqHeader b Connection: keep-alive, xxx
24 BereqHeader b X-Forwarded-For: 192.168.176.1
24 BereqHeader b xxx: yyy
24 BereqHeader b X-Varnish: 23
24 BereqUnset b Connection: keep-alive, xxx
24 BereqHeader b Connection: close
24 VCL_call b PIPE
24 BereqHeader b upgrade: websocketz
24 BereqUnset b Connection: close
24 BereqHeader b connection: keep-alive, xxx
24 VCL_return b pipe
仅供参考,xxx
标题已添加到 Varnish 配置的vcl_recv
例程中,即set req.http.xxx = "yyy";
. 当后端收到此请求时,如果它执行逐跳头删除操作,那么我们应该看到它xxx
消失了。
我对 Varnish 的管道模式的理解意味着这里没有缓存,所以这不太可能提供任何缓存中毒 DoS 的可能性,但是使用此配置的设置可能至少继续提供一种滥用逐跳标头的方法,尽管在像 Varnish 这样强大的缓存层后面。
那么,如何通过逐跳报头滥用使 Varnish 容易受到某种形式的缓存中毒 DoS 的影响呢?我最终得到了以下配置,它转发请求的逐跳标头,并且还应该与缓存兼容:
vcl 4.0;
import var;
backend default {
.host = "foo.bar";
.port = "80";
}
sub vcl_recv {
set req.http.xxx = "yyy";
var.global_set("conn_string", req.http.connection);
}
sub vcl_backend_fetch {
set bereq.http.connection = var.global_get("conn_string");
}
这需要安装 Varnish 的变量 VMOD 扩展。这样做是从请求Connection
头中创建一个全局变量,并将其应用于后端请求。这类似于上面的 Websocket 配置,但这种模式不会阻止缓存。我不知道为什么有人想要像这样合法地将Connection
用户请求中的值复制到后端请求中,但是如果他们这样做了并且后端应用程序对xxx
本示例中使用的标头的删除很敏感,则可能会导致缓存中毒DoS。
有关使用此配置的缓存中毒 DoS 的演示,请参阅以下内容:
Cache poisoning DoS via hop-by-hop header abuse
xxx
应用程序需要 Varnish 添加的标头,如果没有它,则会出现(可缓存的)404 错误。使用的完整设置是:Apache HTTPD 前面的 Varnish 6.3(使用上述配置)(我无法删除 nginx逐跳列表中的标头),它用于ProxyPass
在 gunicorn 上运行的 Flask 应用程序。
在服务器端请求中(通过设计或伪造)
这个有点过时,但请耐心等待。某些系统使用户能够定义将由服务器端执行的请求,例如添加 webhook 或类似的请求。虽然这些包含直接定义连接的逐跳标头列表的能力是不正常的,但如果您能够添加自定义标头,则可以尝试添加Connection
标头并查看它是否与您的逐跳报头。
否则,如果系统中存在可利用的 SSRF 漏洞,添加此技术可能会揭示更多信息或有助于使 SSRF 更具影响力。不过,您可能需要能够将标头与 SSRF 一起注入,这种情况相当罕见。
这并不是真正不同类型的影响,而是针对已经涵盖的相同用途的更多不同启动点 - 即修改来自目标系统而不是您的客户端的请求。
WAF规则绕过
如果系统有一个 WAF 规则,要求在请求中存在一个标头,那么这可以使用逐跳绕过。在这种情况下,WAF 大概不必自己剥离逐跳标头 - 否则它可能会在检查一致性请求之前这样做,这可能不会绕过规则检查。如果 WAF 碰巧忽略了Connection
逐跳列表,并且更好地将其转发到下一跳,那么您应该能够在请求中包含标头以通过 WAF 规则,但也要列出标头作为逐跳,因此下一个代理将其删除,有效地将请求发送到没有标头的后端。
需要进一步研究
感觉这里有更多的东西可以发现,关于能够被这种技术滥用的常见标头,比自定义标头更可靠,以及该技术可能提供的其他用途和影响。测试各种代理和缓存也可能很有用,以查看它们对此的脆弱程度,以及如果它们不是开箱即用的,是否有可能使它们容易受到攻击的配置 - 如上所述,我的测试建议使用 HAProxy 和 Nginx (配置为代理)将转发逐跳列表,而 Apache HTTPD 不会,而 Varnish 不会,除非您真的不遗余力地配置它来这样做,但是我的研究没有深入深入到跨版本和常见配置模式的变化。我在测试流行的缓存/代理服务(如 Cloudflare、Cloudfront 等)方面也没有做太多工作,
原网址: 访问
创建时间: 2021-07-30 23:07:47
目录: 安全研究
标签: 协议