HOST头注入漏洞¶
HOST头的作用是什么?¶
在 HTTP/1.0 时代,一个 IP 地址只能绑定一个网站。但随着互联网的发展,IP 地址变得稀缺,于是 HTTP/1.1 协议引入了 虚拟主机(Virtual Host) 技术,并强制规定 Host 请求头为**必选字段**。
HTTP 主机头的作用 adalah 为了帮助识别客户端想要与哪个后端组件进行通信。如果请求中不包含主机头,或者主机头存在格式错误的情况,那么在处理传入的请求时可能会引发问题。
当多个应用程序可以通过相同的 IP 地址访问时,这通常是由于以下情况之一导致的。
虚拟主机¶
一种可能的场景是,一个 Web 服务器同时托管多个网站或应用程序。这可能意味着多个网站由同一所有者管理,但也存在由不同所有者运营的网站共享同一平台的情况。这种情况比过去更为少见,但一些基于云的服务式软件解决方案仍然会采用这种架构。
无论哪种情况,虽然这些不同的网站都有各自不同的域名,但它们都共享同一个 IP 地址,该地址指向服务器。以这种方式在单个服务器上托管的网站被称为“虚拟主机”。
对于正常访问该网站的用户来说,虚拟主机通常与其独立托管在专用服务器上的网站并无区别。
通过中间节点来路由流量¶
另一个常见的场景是,当网站托管在不同的后端服务器上时,但客户端与服务器之间的所有通信都通过某种中间系统进行路由。这个中间系统可能是一个简单的负载均衡器,也可能是某种反向代理服务器。这种布局在客户端通过内容分发网络访问网站时尤为常见。
在这种情况下,尽管这些网站托管在不同的后端服务器上,但它们的所有域名仍然都能解析到同一台中间服务器的 IP 地址。这带来了与虚拟主机类似的问题:反向代理或负载均衡器需要知道应该将每个请求路由到哪个后端服务器。
核心作用总结¶
它的核心作用可以总结为以下几点:
- 区分虚拟主机(最核心作用): 当多个域名(如
www.a.com和www.b.com)解析到同一个服务器 IP 时,服务器收到请求后,必须通过读取Host头来判断客户端到底想访问哪个网站,从而返回对应的网页内容。 - 反向代理与负载均衡路由: 在 Nginx、HAProxy 等网关层,
Host头是决定将请求转发给后端哪个微服务或哪个服务器集群的关键路由依据。 - 生成绝对 URL 的参考基准: 当服务器需要生成一个完整的 URL(如 302 重定向的
Location头、邮件中的链接、OAuth 回调地址)时,需要知道当前请求的“基准域名”是什么,Host头提供了这个信息。
为什么开发者要读取客户端的HOST头?¶
开发者直接读取 Host 头(如 PHP 的 $_SERVER['HTTP_HOST'],Node.js 的 req.headers.host,Java 的 request.getServerName())来拼接 URL,通常是出于以下**合理的业务和技术需求**:
1. 多环境部署的“自适应”需求(Dev / Test / Prod)¶
这是最常见的原因。一套代码需要运行在开发环境、测试环境和生产环境。
- 如果**硬编码**域名(如写死
https://www.prod.com),那么在测试环境触发密码重置时,发给测试用户的邮件链接会指向生产环境,导致测试失败或污染生产数据。 - 如果**动态读取**
Host头,代码就能“自适应”:在测试环境读到test.com,在生产环境读到prod.com,无需修改代码即可完美运行。
2. SaaS 架构与多租户/多域名支持¶
很多 SaaS 系统允许客户绑定自己的独立域名(例如 crm.clientA.com 和 crm.clientB.com,甚至 app.clientC.com 都指向同一个后端)。 后端系统有成千上万个客户域名,不可能在配置文件中穷举所有域名。因此,后端只能根据当前请求的 Host 头,动态生成属于该租户的正确 URL。
3. 保持反向代理后的 URL 一致性¶
现代架构通常是:用户 -> CDN/WAF -> Nginx反向代理 -> 后端应用。 后端应用运行在内部网络(如 http://127.0.0.1:8080)。当后端需要生成重定向 URL 时,如果直接读取内部 IP,生成的链接用户将无法访问。读取外部传入的 Host 头(配合 X-Forwarded-Proto),可以确保生成的 URL 与用户浏览器地址栏中的 URL 保持一致(如 https://www.xxx.com)。
4. 框架 API 的“便利性”陷阱¶
大多数 Web 框架都提供了极其方便的 API 来获取当前请求的上下文信息。开发者在编写代码时,看到 request.host 或 request.getBaseUrl() 这样的方法,会自然而然地认为这是“获取当前系统环境信息”的标准做法,从而直接调用,没有意识到这其实是“读取客户端输入”。
Note
由于 Host 头部实际上是用户可以控制的参数,因此这种做法可能会引发一系列问题。如果输入内容没有正确转义或验证,那么 Host 头部就成为了利用其他漏洞的潜在途径,其中最典型的例子就是:
- Web缓存中毒
- 特定功能中的业务逻辑缺陷
- 基于路由的SSRF漏洞
- 典型的服务器端漏洞,例如 SQL 注入攻击
如何识别HOST头注入漏洞¶
传入一个任意的HOST头¶
在检测主机头注入漏洞时,第一步是测试当在主机头中传入一个任意的、未被识别的域名时会发生什么情况。
一些拦截代理程序直接从主机头中提取目标 IP 地址,这使得这种测试几乎变得不可能;因为对头部所做的任何修改都会导致请求被发送到完全不同的 IP 地址。然而,Burp Suite 能够准确保持主机头与目标 IP 地址之间的分离。这种分离使得你可以提供任何你想要的主机头,同时仍然确保请求能够发送到预定的目标地址。
Tip
目标 URL 会显示在面板顶部(适用于 Burp Repeater 和 Proxy 拦截功能),或者出现在 Burp Intruder 的“目标”选项卡中。你可以点击铅笔图标来手动编辑目标 URL。
有时候,即使提供了不期望的主机头信息,你仍然可以访问目标网站。这种情况可能有多种原因。例如,服务器通常会被配置有一个默认选项,以便在收到无法识别的域名请求时仍能正常处理这些请求。如果你的目标网站恰好是默认选项,那么你就幸运了。在这种情况下,你可以研究应用程序对主机头信息的处理方式,以及这种行为是否可以被利用。
另一方面,由于主机头是网站运行的基本组成部分,因此如果对其进行修改,往往会导致无法访问目标应用程序。接收你请求的前端服务器或负载均衡器可能根本不知道如何将请求转发到哪里,从而导致“ Invalid Host header ”之类的错误。如果目标是通过 CDN 访问的,这种情况就更有可能发生。在这种情况下,你应该尝试使用下面列出的一些解决方法。
检查是否有无效的验证信息¶
除了收到“ Invalid Host header ”的响应之外,还可能是因为某种安全措施的原因,你的请求被阻止了。例如,一些网站会验证主机头是否与 TLS 握手过程中的 SNI 信息匹配。但这并不一定意味着这些网站能够免受主机头攻击的影响。
你应该尝试理解该网站是如何解析 Host 头信息的。有时候,通过这种方式可以发现一些可以被利用来绕过验证的漏洞。例如,某些解析算法会忽略 Host 头中的端口信息,这样一来,只有域名会被验证。如果你能够提供一个非数字形式的端口号,就可以让域名保持原样,从而确保能够访问目标应用程序,同时有可能通过该端口注入恶意代码。
其他网站也会尝试使用类似的逻辑来处理任意子域名的注册问题。在这种情况下,你可以通过注册一个以与白名单中的域名相同字符序列结尾的任意域名来完全绕过验证过程。
或者,你可以利用那些已经被你攻破的、安全性较低的子域。
发送具有模糊性的请求¶
用于验证宿主机的代码以及那些对宿主机进行操作的代码,往往存在于不同的应用程序组件中,甚至分布在不同的服务器上。通过发现并利用它们在获取宿主机头信息时的差异,就可以发出一种模糊的请求,而这种请求的效果取决于哪个系统来处理该请求。
以下是一些你可以用来创建模糊请求的例子。
插入重复的主机头信息¶
一种可能的办法是尝试添加重复的 Host 头部信息。当然,这样做往往会导致请求被拒绝。不过,由于浏览器不太可能会发送这样的请求,因此有时开发者可能会忽略这种场景。在这种情况下,可能会观察到一些有趣的行为特性。
不同的系统和技术在处理这种情况时会采取不同的方式。但通常情况下,会优先处理其中一个头部信息,从而覆盖另一个头部信息的值。当系统对哪个头部信息是正确的存在分歧时,就可能会出现差异,而这些差异可以被利用来达成目的。考虑以下请求:
假设前端优先选择第一个头部信息,而后端则更倾向于使用最后一个头部信息。在这种情况下,你可以先使用第一个头部信息来确保请求被正确地路由到目标位置,然后使用第二个头部信息将有效载荷传递给服务器端代码。
提供一个绝对路径¶
虽然请求行通常会指定目标域上的相对路径,但许多服务器也能够理解绝对 URL 的请求。
由于同时提供了绝对 URL 和主机头信息,这就导致了不确定性。这种模糊性可能会导致不同系统之间的数据不一致。按照规范,在路由请求时应该优先考虑请求行的内容,但实际上并不总是如此。你可能会利用这些差异,就像使用重复的主机头一样。
请注意,您可能需要尝试不同的协议。因为根据请求行是包含 HTTP 还是 HTTPS URL,服务器的行为会有所不同。
添加行尾换行¶
你还可以通过用空格字符将 HTTP 头部的内容缩进来发现一些奇特的行为。有些服务器会将缩进的头部内容视为一个包装过的行,从而将其视为前面那个头部值的一部分。而另一些服务器则完全会忽略这种缩进的头部内容。
由于处理这个案件的方式存在很大差异,因此不同系统在处理您的请求时往往会产生不一致的情况。例如,以以下请求为例:
该网站可能会阻止包含多个主机头部的请求,但您可以通过这种方式对其中一个头部进行缩排来绕过这一验证。如果前端忽略这个缩排后的头部,那么该请求将被视为普通的 vulnerable-website.com 请求而被处理。现在假设后端忽略前面的空格,而优先处理重复情况中的第一个头部。这种差异可能允许您通过“包装后的”主机头部传递任意值。
主机头覆盖¶
即使你无法通过模糊的请求来覆盖主机头的值,仍然有其他方法可以在不修改主机头本身的情况下改变其值。这可以通过利用其他几个旨在用于此目的的 HTTP 头信息来实现,尽管这些头信息通常适用于更非正式的用途。
正如我们已经讨论过的,网站通常是通过某种中间系统来访问的,比如负载均衡器或反向代理。在这种架构中,后端服务器接收到的主机头中可能包含这些中间系统的域名。不过,这对于所请求的功能来说通常并不重要。
为了解决这个问题,前端可以插入 X-Forwarded-Host 头部信息,其中包含了客户端初始请求中 Host 头部原始的值。因此,当存在 X-Forwarded-Host 头部时,许多框架都会使用它来代替原来的头部信息。即使没有使用该头部的前端,也可能会出现这种行为。
有时候,你可以使用 X-Forwarded-Host 来插入恶意代码,同时避免对主机头部数据进行任何验证。
虽然 X-Forwarded-Host 是这种行为的默认标准,但你可能会遇到其他具有类似功能的头部信息,包括:
Tip
在 Burp Suite 中,你可以利用 Param Miner 扩展程序的“猜测头部信息”功能,通过它内置的丰富词表来自动探测受支持的头部信息。
从安全角度来看,需要指出的是,一些网站,甚至包括你的网站,可能会在不注意的情况下支持这种行为。这通常是因为某些第三方技术默认启用了其中一个或几个头部信息。
如何利用HOST头注入进行攻击¶
密码重置漏洞¶
在密码重置(Password Reset)场景中,Host注入漏洞(Host Header Injection)是导致**账户接管(Account Takeover, ATO)** 的最经典、最危险的漏洞之一
漏洞原理¶
正常的密码重置流程¶
当用户在网站点击“忘记密码”时,后端的正常处理逻辑如下:
1. 用户提交用户名/邮箱。
2. 服务器生成一个随机的重置 Token(如 a1b2c3d4)。
3. 服务器拼接重置链接。(关键点)
4. 将包含该链接的邮件发送到用户的注册邮箱。
5. 用户点击链接,携带 Token 访问服务器,验证通过后修改密码。
漏洞是如何产生的?¶
问题出在**第3步(拼接链接)**。
许多开发者为了让代码支持多环境(开发、测试、生产)或多域名部署,没有将域名“硬编码”在代码中,而是**动态读取当前 HTTP 请求头中的 Host 字段**来拼接 URL。
存在漏洞的后端伪代码示例:
# 危险做法:直接读取请求头中的 Host
current_host = request.headers.get('Host')
reset_url = f"http://{current_host}/reset-password?token={generated_token}"
send_email(user_email, f"请点击链接重置密码: {reset_url}")
由于 HTTP 协议允许客户端(浏览器或攻击者工具)随意修改 Host 头,如果服务端没有对 Host 头进行白名单校验,攻击者就可以将 Host 篡改为自己的域名。
详细利用过程¶
假设目标网站为 www.victim.com,攻击者域名为 attacker.com。
准备与拦截请求¶
攻击者在目标网站注册一个自己控制的账号(如 test@test.com),并触发“忘记密码”功能。
此时,攻击者使用抓包工具(如 Burp Suite)拦截发送重置请求的 HTTP 数据包。
正常的 HTTP 请求:
POST /api/forgot-password HTTP/1.1
Host: www.victim.com
Content-Type: application/json
{"email": "test@test.com"}
篡改 Host 头¶
攻击者在抓包工具中,将 Host 头的值修改为攻击者控制的域名 attacker.com,然后放行请求。
篡改后的恶意 HTTP 请求:
POST /api/forgot-password HTTP/1.1
Host: attacker.com <--- 恶意篡改
Content-Type: application/json
{"email": "test@test.com"}
服务器处理与发送邮件¶
服务器接收到请求后,读取到 Host: attacker.com。
后端代码执行拼接:http://attacker.com/reset-password?token=8f7e6d5c4b3a。
随后,服务器将包含这个**恶意链接**的密码重置邮件发送给了 test@test.com。
受害者(或攻击者测试时)收到的邮件内容:
您好,我们收到了您的密码重置请求。 请点击以下链接重置您的密码(15分钟内有效): http://attacker.com/reset-password?token=8f7e6d5c4b3a
窃取 Token¶
- 在漏洞验证阶段(PoC): 攻击者自己登录
test@test.com邮箱,点击该链接。由于链接指向attacker.com,请求会发送到攻击者的服务器。攻击者在自己的服务器日志中看到了完整的 URL,从而获取了 Token:8f7e6d5c4b3a。 - 在真实攻击阶段: 攻击者需要诱导真实用户(如管理员)点击这封邮件。如果用户点击了,Token 就会泄露给攻击者。(注:有时攻击者会利用缓存投毒,让用户在正常页面看到恶意链接,但这属于更复杂的变种)。
完成账户接管¶
攻击者获取到合法的 Token 后,直接向**真实的服务器**发起修改密码的请求:
POST /api/reset-password HTTP/1.1
Host: www.victim.com
Content-Type: application/json
{
"token": "8f7e6d5c4b3a",
"new_password": "Hacker@123"
}
test@test.com 的密码修改为 Hacker@123。攻击者成功接管该账户。
常见的变种与绕过技巧¶
在实际渗透测试中,如果直接修改 Host 头被 WAF 拦截或服务器报错,攻击者通常会尝试以下变种:
- 利用其他 HTTP 头: 很多框架或反向代理(如 Nginx, Apache)在配置不当时,会优先读取
X-Forwarded-Host、X-Host、X-Forwarded-Server等头部。攻击者会尝试注入这些头部。 - 注入换行符(CRLF): 在某些存在缺陷的邮件发送组件中,攻击者可能在 Host 头中注入
\r\n,从而篡改邮件的其他部分(如 BCC 密送给自己),但这属于 CRLF 注入与 Host 注入的结合。 - 利用端口或子域名: 如果系统校验了主域名,攻击者可能会尝试
www.victim.com@attacker.com或attacker.com#www.victim.com等畸形格式,试图绕过正则校验。
利用HOST注入来进行Web缓存中毒攻击¶
在排查潜在的主机头攻击时,经常会发现一些看似存在漏洞的行为,但实际上并不容易被利用。例如,可能会发现主机头以未经过 HTML 编码的形式出现在响应中,或者甚至被直接用于脚本中。那些由主机头引起的反射型客户端漏洞,如 XSS 漏洞,通常是不容易被利用的。攻击者无法以有效的方式强制受害者的浏览器显示错误的主机名。
不过,如果目标系统使用了网络缓存,那么就有可能将这种无用的、被反射的漏洞转化为一种危险的、已存储的漏洞。只要说服缓存向其他用户发送有问题的响应,就能实现这一点。
要实施网络缓存注入攻击,就需要从服务器获取包含注入 payload 的响应。难点在于如何在保留缓存键的同时实现这一目标,因为该缓存键仍然会映射到其他用户的请求上。如果成功的话,下一步就是将该恶意响应缓存起来,这样当任何试图访问受感染页面的用户请求时,就会收到该响应。
独立缓存通常会在缓存键中包含主机头信息,因此这种方法在集成式的应用级缓存中效果最佳。不过,前面讨论的技术有时也能用于攻击即便是独立的网络缓存。
经典的服务端漏洞¶
每个 HTTP 头都可能是利用经典服务器端漏洞的载体,Host 头也不例外。例如,可以通过 Host 头使用常见的 SQL 注入攻击手段进行攻击。如果将该头中的值传递给 SQL 语句,那么这种漏洞就有可能被利用。
通过HOST头进行虚拟主机的暴力破解¶
有些公司会犯一个错误,即在同一台服务器上同时托管公开可访问的网站和私密的内部网站。通常情况下,服务器会同时拥有公共 IP 地址和私有 IP 地址。由于内部站点的主机名可能会指向私有 IP 地址,因此仅通过查看 DNS 记录并不能总是发现这种问题。
在某些情况下,内部网站甚至可能没有任何公共 DNS 记录。不过,攻击者通常可以访问任何他们有访问权限的服务器上的任何虚拟主机。只要他们能够猜出主机名,他们就能成功访问这些主机。如果他们通过其他方式发现了某个隐藏的域名,比如从信息泄露中得知了它的名称,那么他们可以直接请求访问该域名。否则,他们可以使用像 Burp Intruder 这样的工具,通过简单的候选子域名列表来 brute-force 攻击这些虚拟主机。
基于路由的SSRF漏洞¶
经典的 SSRF 漏洞通常源于 XXE 漏洞或可利用的业务逻辑,这些逻辑会向由用户可控制的输入生成的 URL 发送 HTTP 请求。而基于路由的 SSRF 漏洞则依赖于利用许多云架构中常见的中间组件,这些组件包括内部负载均衡器和反向代理等。
虽然这些组件被部署起来是为了实现不同的功能,但从根本上来说,它们都负责接收请求并将其传递给相应的后端系统。如果它们的配置存在安全漏洞,以至于能够根据未经验证的“Host 头字段”来转发请求,那么这些组件就有可能被操纵,将请求错误地路由到攻击者所选择的任意系统上。
这些系统是非常有效的攻击目标。它们位于一个得天独厚的网络位置,能够直接接收来自公共网络的请求,同时还能访问大部分甚至全部内部网络资源。因此,主机头成为了进行 SSRF 攻击的绝佳工具,有可能将普通的负载均衡器转变为通往整个内部网络的网关。
你可以使用 Burp Collaborator 来辅助识别这些漏洞。如果在主机头中提供了 Collaborator 服务器的域名,然后收到目标服务器或其他路径系统中的 DNS 查询结果,这表明你可能能够将请求路由到任意域名。
已经确认可以成功操控中间系统,将请求路由到任意公共服务器之后,下一步就是看看能否利用这一特性访问那些仅允许内部访问的系统。为此,你需要找到目标内部网络中正在使用的私有 IP 地址。除了应用程序泄露的 IP 地址之外,还可以扫描公司所属主机的域名,看看是否有域名对应着私有 IP 地址。如果以上方法都无效,那么还可以通过暴力尝试标准的私有 IP 范围来找到有效的 IP 地址,比如 192.168.0.0/16 。
Note
更多参考:https://portswigger.net/research/cracking-the-lens-targeting-https-hidden-attack-surface
连接状态攻击¶
出于性能考虑,许多网站会重复使用与同一客户端相关的连接,以处理多次请求/响应周期。然而,如果 HTTP 服务器配置不当,可能会基于这样一个危险的假设来运作:通过同一连接发送的所有 HTTP/1.1 请求,其头部信息都是相同的。虽然对于由浏览器发送的请求来说情况可能如此,但来自 Burp Repeater 的一系列请求却未必如此。这可能会导致一系列潜在的问题。
例如,有时你会遇到一些服务器只会在新连接下的第一个请求时进行彻底验证。在这种情况下,你可以通过发送一个看似无害的初始请求,然后在同一连接上发送一个恶意的请求来绕过这种验证机制。
许多反向代理使用主机头来将请求路由到正确的后端服务器。它们假设连接上的所有请求都指向与初始请求相同的主机。这一机制为多种主机头攻击提供了有效的手段,包括基于路由的 SSRF 攻击、密码重置攻击以及缓存攻击等。
畸形请求导致SSRF漏洞¶
自定义的代理服务器有时无法正确验证请求行的内容,这可能会导致输入包含异常或格式不正确的数据,从而引发不良后果。
例如,反向代理可能会处理请求路径,在路径前加上 http://backend-server 作为前缀,然后将请求路由到相应的上游 URL。如果路径以 / 字符开头,这种方法效果很好。但是,如果路径以 @ 字符开头呢?那时该怎么办呢?
最终生成的上行 URL 将是 http://backend-server@private-intranet/example 。大多数 HTTP 库都将这个 URL 解释为一条请求,用于访问 private-intranet ,同时用户名为 backend-server 。
如何防御HOST头注入漏洞¶
为了防止 HTTP 主机头攻击,最简单的办法就是在服务器端代码中完全避免使用主机头。请仔细检查每个 URL 是否真的需要作为绝对路径。通常你会发现,使用相对路径就足够了。这个简单的改动就能帮助你避免网络缓存中毒漏洞的威胁。
预防 HTTP 主机头攻击的其他方法包括:
保护绝对路径的 URL¶
当你需要使用绝对 URL 时,应该要求在配置文件中手动指定当前域名,并引用该值而不是 Host 头字段。这样就能避免密码重置注入攻击的威胁。
验证主机头信息是否正确¶
如果你必须使用主机头,那么请确保正确验证它。这通常需要将主机头与允许使用的域名列表进行比对,对于不匹配的主机头请求则进行拒绝或重定向处理。你可以通过查阅相关框架的文档来获取关于如何实施这一操作的指导。例如,Django 框架在设置文件中提供了 ALLOWED_HOSTS 选项。这种方法能够有效降低主机头注入攻击的风险。
请不要支持“主机头覆盖”功能。¶
同样重要的是要检查系统是否支持用于构造这些攻击的额外头部信息,特别是 X-Forwarded-Host 这样的头部。需要注意的是,这些头部信息可能是默认支持的。
小心处理内网虚拟主机¶
在使用虚拟主机时,应避免将仅面向内部的网站和应用程序与公开内容放在同一台服务器上。否则,攻击者可能通过操纵主机头来访问内部域名。