HTTP主机标头攻击
在本节中,我们将讨论错误配置和有缺陷的业务逻辑如何通过HTTP Host标头使网站遭受各种攻击。我们将概述识别主机标头漏洞的高级方法,并演示如何利用它们。最后,我们将提供一些常规指导,指导您如何保护自己的网站免受此类攻击。
什么是HTTP Host标头?
从HTTP / 1.1开始,HTTP Host标头是必需的请求标头。它指定客户端要访问的域名。例如,当用户访问时https://portswigger.net/web-security
,他们的浏览器将组成一个包含Host标头的请求,如下所示:
GET /web-security HTTP/1.1Host: portswigger.net
在某些情况下,例如当请求已由中介系统转发时,主机值可能会在到达预期的后端组件之前进行更改。我们将在下面更详细地讨论这种情况。
HTTP Host标头的目的是什么?
HTTP Host标头的目的是帮助识别客户端想要与之通信的后端组件。如果请求不包含Host标头,或者Host标头以某种方式格式不正确,则在将传入请求路由到预期的应用程序时可能会导致问题。
从历史上看,这种歧义并不存在,因为每个IP地址只会托管单个域的内容。如今,很大程度上由于基于云的解决方案和将许多相关体系结构外包的趋势不断增长,通常可以在同一IP地址访问多个网站和应用程序。这种方法的普及也部分是由于IPv4地址耗尽所致。
当可以通过同一IP地址访问多个应用程序时,最常见的原因是以下情况之一。
虚拟主机
一种可能的情况是,单个Web服务器托管多个网站或应用程序。这可能是具有单个所有者的多个网站,但是也可能将具有不同所有者的网站托管在单个共享平台上。这种情况比以前少见,但在某些基于云的SaaS解决方案中仍然会发生。
无论哪种情况,尽管这些不同的网站中的每一个都将具有不同的域名,但是它们都与服务器共享一个公共IP地址。以这种方式在单个服务器上托管的网站称为“虚拟主机”。
对于访问该网站的普通用户来说,虚拟主机通常与托管在其专用服务器上的网站是无法区分的。
通过中介路由流量
另一个常见的情况是,网站托管在不同的后端服务器上,但是客户端和服务器之间的所有流量都通过中介系统进行路由。这可能是一个简单的负载平衡器或某种反向代理服务器。在客户通过内容分发网络(CDN)访问网站的情况下,这种设置尤其普遍。
在这种情况下,即使网站托管在单独的后端服务器上,它们的所有域名也都解析为中间组件的单个IP地址。这带来了与虚拟主机相同的挑战,因为反向代理或负载平衡器需要知道将每个请求路由到的适当后端。
HTTP Host标头如何解决此问题?
在这两种情况下,都依赖于Host标头来指定预期的收件人。一个常见的类比是向居住在公寓楼中的某人发送一封信的过程。整个建筑物具有相同的街道地址,但是在该街道地址后面有许多不同的公寓,每套公寓都需要以某种方式接收正确的邮件。解决此问题的一种方法就是简单地在地址中包含公寓号码或收件人的名字。对于HTTP消息,Host标头起类似的作用。
当浏览器发送请求时,目标URL将解析为特定服务器的IP地址。当该服务器接收到请求时,它参考主机头来确定预期的后端并相应地转发该请求。
什么是HTTP主机标头攻击?
HTTP主机标头攻击利用易受攻击的网站,这些网站以不安全的方式处理主机标头的值。如果服务器隐式信任Host标头,但未能正确验证或转义它,则攻击者可能能够使用此输入来注入有害的有效负载,以操纵服务器端的行为。涉及直接将有效负载注入主机标头的攻击通常称为“主机标头注入”攻击。
现成的Web应用程序通常不知道将它们部署在哪个域上,除非在安装过程中在配置文件中手动指定了该域。例如,当他们需要知道当前域以生成电子邮件中包含的绝对URL时,他们可以求助于Host头中的域:
标头值还可以用于网站基础结构的不同系统之间的各种交互。
由于Host标头实际上是用户可控制的,因此这种做法可能导致许多问题。如果没有正确地对输入进行转义或验证,则Host标头是利用一系列其他漏洞的潜在载体,最明显的是:
- Web缓存中毒
- 特定功能中的 业务[逻辑缺陷]
- 基于路由的SSRF
- 经典的服务器端漏洞,例如SQL注入
HTTP Host标头漏洞如何产生?
HTTP主机标头漏洞通常是由于错误的假设而造成的,即该标头不可由用户控制。即使攻击者可以使用Burp Proxy之类的工具轻松地修改主机标头,这也会在Host标头中创建隐式信任,并导致其验证或转义不足。
即使可以更安全地处理Host标头本身,具体取决于处理传入请求的服务器的配置,也可以通过注入其他标头来覆盖Host。有时,网站所有者不知道默认情况下支持这些标头,因此,它们可能不会受到相同级别的审查。
实际上,这些漏洞中的许多漏洞并不是由于编码不安全,而是由于相关基础架构中一个或多个组件的配置不安全。由于网站将第三方技术集成到其体系结构中,而不必了解配置选项及其安全隐患,因此可能会发生这些配置问题。
利用HTTP主机标头漏洞
到目前为止,您应该已经对HTTP Host标头有了一个很好的了解。对于渗透测试者和bug赏金猎人,我们创建了一些其他指南,指导您如何自行识别和利用这些漏洞。我们还提供了一些故意易受攻击的LABS,以便您可以练习其中的一些技术。
[如何识别和利用HTTP Host标头漏洞]
如何防止HTTP主机标头攻击
为防止HTTP主机头攻击,最简单的方法是避免在服务器端代码中完全使用主机头。仔细检查每个URL是否真的需要是绝对的。您经常会发现您可以只使用相对URL。这个简单的更改可以帮助您特别防止[Web缓存中毒]漏洞。
防止HTTP主机标头攻击的其他方法包括:
保护绝对网址
当必须使用绝对URL时,应要求在配置文件中手动指定当前域,并引用此值而不是Host标头。例如,这种方法将消除密码重置中毒的威胁。
验证主机头
如果必须使用Host标头,请确保正确验证它。这应该包括对照允许域的白名单检查它,以及拒绝或重定向对无法识别的主机的任何请求。您应查阅框架文档以获取有关执行此操作的指导。例如,Django框架ALLOWED_HOSTS
在设置文件中提供了该选项。这种方法将减少您遭受主机标头注入攻击的风险。
不支持主机替代标题
同样重要的是要检查您是否不支持可能用于构造这些攻击的其他标头,尤其是X-Forwarded-Host
。请记住,默认情况下可能支持这些功能。
将允许的域名列入白名单
为了防止对内部基础结构进行基于路由的攻击,应将负载平衡器或任何反向代理配置为仅将请求转发到允许域的白名单。
注意仅限内部使用的虚拟主机
使用虚拟托管时,应避免将面向内部的网站和应用程序与面向公众的内容托管在同一服务器上。否则,攻击者可能可以通过主机标头操纵来访问内部域。
如何识别和利用HTTP Host标头漏洞
如何使用HTTP Host标头测试漏洞
要测试网站是否容易受到HTTP Host标头的攻击,您将需要拦截代理(例如Burp Proxy)和手动测试工具(例如Burp Repeater和Burp Intruder)。
简而言之,您需要确定您是否能够修改Host标头,并且仍然能够根据您的请求到达目标应用程序。如果是这样,则可以使用此标头来探测应用程序,并观察它对响应有什么影响。
提供一个任意的主机头
在探查主机头注入漏洞时,第一步是测试当您通过主机头提供任意的,无法识别的域名时发生的情况。
一些拦截代理直接从Host标头获取目标IP地址,这使得这种测试几乎不可能。您对标头所做的任何更改都只会导致将请求发送到完全不同的IP地址。但是,Burp Suite可以准确地维护主机标头和目标IP地址之间的分隔。这种分隔允许您提供所需的任何任意或格式错误的Host标头,同时仍然确保将请求发送到预期的目标。
目标URL显示在面板顶部(用于Burp转发器和代理拦截)或Burp Intruder中的“ Target”选项卡上。您可以通过单击铅笔图标手动编辑目标。
有时,即使您提供了意外的Host标头,您仍然仍然可以访问目标网站。这可能是出于多种原因。例如,如果服务器收到无法识别的域名请求,则有时会为其配置默认或后备选项。如果您的目标网站碰巧是默认网站,那么您很幸运。在这种情况下,您可以开始研究应用程序使用Host标头执行的操作以及此行为是否可利用。
另一方面,由于Host标头是网站工作方式的基本组成部分,因此对其进行篡改通常意味着您将根本无法访问目标应用程序。收到您的请求的前端服务器或负载平衡器可能根本不知道将请求转发到何处,从而导致某种“ Invalid Host header
”错误。如果通过CDN访问目标,则尤其可能发生这种情况。在这种情况下,您应该继续尝试下面概述的一些技术。
检查验证错误
Invalid Host header
您可能会发现由于某种安全措施而导致您的请求被阻止, 而不是收到“ ”响应。例如,某些网站将验证Host标头是否与TLS握手中的SNI相匹配。这并不一定意味着它们不受Host标头攻击。
您应该尝试了解网站如何解析Host标头。有时,这可能会发现可用于绕过验证的漏洞。例如,某些解析算法将从Host标头中省略端口,这意味着仅域名被验证。如果还能够提供非数字端口,则可以保持域名不变,以确保到达目标应用程序,同时可能通过该端口注入有效负载。
GET /example HTTP/1.1Host: vulnerable-website.com:bad-stuff-here
其他站点将尝试应用匹配逻辑以允许任意子域。在这种情况下,您可以通过注册一个以与白名单中相同的字符序列结尾的任意域名来完全绕过验证:
GET /example HTTP/1.1Host: notvulnerable-website.com
另外,您可以利用已经受到破坏的安全性较低的子域:
GET /example HTTP/1.1Host: hacked-subdomain.vulnerable-website.com
有关常见域验证缺陷的更多示例,请查看我们的内容,以规避常见的SSRF防御和Origin标头解析错误。
发送模糊的请求
验证主机的代码和对其进行脆弱处理的代码通常位于不同的应用程序组件中,甚至位于单独的服务器上。通过识别和利用它们在检索主机标头方面的差异,您可以发出模棱两可的请求,该请求似乎具有不同的主机,具体取决于正在查看的系统。
注入重复的主机头
一种可能的方法是尝试添加重复的Host标头。诚然,这通常只会导致您的请求被阻止。但是,由于浏览器不太可能发送此类请求,因此您有时可能会发现开发人员没有预料到这种情况。在这种情况下,您可能会暴露一些有趣的行为怪癖。
不同的系统和技术将以不同的方式处理这种情况,但是通常会给两个标头之一优先于另一个标头,从而有效地覆盖其值。当系统不同意哪个标头是正确的标头时,这可能会导致差异,您也许可以利用。考虑以下请求:
GET /example HTTP/1.1Host: vulnerable-website.comHost: bad-stuff-here
假设前端将优先级设置为标头的第一个实例,但后端优先选择最终实例。在这种情况下,您可以使用第一个标头来确保将请求路由到预期的目标,并使用第二个标头将有效负载传递到服务器端代码中。
提供一个绝对URL
尽管请求行通常在请求的域上指定相对路径,但是许多服务器也配置为了解对绝对URL的请求。
同时提供绝对URL和Host标头导致的歧义也可能导致不同系统之间的差异。正式地,在路由请求时,应优先考虑请求行,但实际上并非总是如此。您可能会以与重复的Host标头相同的方式利用这些差异。
GET https://vulnerable-website.com/ HTTP/1.1Host: bad-stuff-here
请注意,您可能还需要尝试不同的协议。服务器有时会根据请求行包含HTTP还是HTTPS URL而有所不同。
添加换行
您还可以通过缩进带有空格字符的HTTP标头来发现古怪的行为。某些服务器会将缩进的标头解释为换行,因此将其视为前一个标头值的一部分。其他服务器将完全忽略缩进的标头。
由于这种情况的处理方式非常不一致,因此处理您的请求的不同系统之间通常会存在差异。例如,考虑以下请求:
GET /example HTTP/1.1 Host: bad-stuff-hereHost: vulnerable-website.com
该网站可能会阻止带有多个主机标头的请求,但是您可以通过缩进其中的一个缩略来绕过此验证。如果前端忽略缩进的标头,则该请求将作为的普通请求处理vulnerable-website.com
。现在,假设后端忽略前导空间,并且在重复的情况下将优先处理第一个标头。这种差异可能使您可以通过“包装的”主机头传递任意值。
其他技巧
这只是发出有害的,模棱两可的请求的许多可能方式的一小部分。您还可以采用多种HTTP请求走私技术来构造主机标头攻击。
注入主机覆盖头
即使您不能使用歧义的请求覆盖Host标头,也有其他方法可以覆盖它的值,同时保持原样。这包括通过旨在满足此目的而设计的其他几个HTTP标头之一注入有效负载,即使对于更多无辜的用例也是如此。
正如我们已经讨论的那样,通常可以通过某种中间系统(例如负载平衡器或反向代理)访问网站。在这种体系结构中,后端服务器接收的Host标头可能包含这些中间系统之一的域名。这通常与请求的功能无关。
为了解决此问题,前端可以注入X-Forwarded-Host
标头,其中包含来自客户端初始请求的Host标头的原始值。因此,当出现X-Forwarded-Host
标头时,许多框架将改为引用它。即使没有前端使用此标头,您也可能会观察到此行为。
有时您可以X-Forwarded-Host
注入恶意输入,同时绕过Host标头本身的任何验证。
GET /example HTTP/1.1Host: vulnerable-website.comX-Forwarded-Host: bad-stuff-here
尽管这X-Forwarded-Host
是此行为的事实上的标准,但是您可能会遇到其他具有类似目的的标头,包括:
X-Host
X-Forwarded-Server
X-HTTP-Host-Override
Forwarded
T /example HTTP/1.1Host: vulnerable-website.comX-Forwarded-Host: bad-stuff-here
尽管这`X-Forwarded-Host`是此行为的事实上的标准,但是您可能会遇到其他具有类似目的的标头,包括:
- `X-Host`
- `X-Forwarded-Server`
- `X-HTTP-Host-Override`
- `Forwarded`