httphost头攻击风险分析

声 明

本文由Tide安全团队成员“Tide”首发于FreeBuf TideSec专栏:

?name=TideSec

文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!

1http host简单介绍

我们在对系统进行抓包时,通常会发现Http请求header(比如在php里是)信息里面会带有一个Host字段,首先简单介绍下该host字段是什么。

比如一个IP地址可以对应多个域名,比如有这么几个域名www.1.com,www.2.com和www.3.com然后在域名提供商那通过A记录或者CNAME记录的方式最终都和我的虚拟机服务器IP 1.1.1.1.1关联起来,那么我通过任何一个域名去访问最终解析到的都是IP 1.1.1.1。我们甚至可尝试在1.1.1.1上设置更多的网站,让更多的域名解析到一个ip地址上,但是问题来了,应该如何区分不同域名显示的不同内容呢?此时就应该用到host概念了,所有的域名都解析到同一个ip地址,但是每个域名又对应不同的host,通过host可以区分出访问的是服务器上的哪个站点。

2http host攻击详解

许多web应用程序都依赖于HTTP的host头来解读出“他们在何处”。可不幸的是,许多应用程序开发人员没有意识到HTTP的host头是由用户所控制的。正如你可能已经知道的那样,在应用程序的安全理念中,用户的输入应该总是被认为不安全的。因此,在未被正确地得到事先验证之前,请永远不要去信任它们。

开发人员一般是依赖HTTP Host header(比如在php里是_SERVER["HTTP_HOST"] ),而这个header很多情况下是靠不住的。而很多应用是直接把这个值不做html编码便输出到了页面中,比如:

还有的地方还包含有token,

这样处理问题一般会很容易遭遇到两种常见的攻击:缓存污染和密码重置。

2.1缓存污染

网页缓存的中毒是指攻击者使用该技术来操控网页缓存,并向任何请求页面的人提供带毒的内容。

对于这种情况的发生,攻击者需要使一台缓存代理中毒,而该缓存可以运作在网站本身、或是下游供应商、内容分发网络(CDNs)、连锁合作商或是其他客户机和服务器之间的缓存机制中。该缓存随后将带毒的内容提供给任何请求它的人,而受害者面对这些所提供的恶意内容,无论如何都是没有控制权的。

为了能使缓存能将污染后的response返回给用户,我们还必须让缓存服务器看到的host header 和应用看到的host header 不一样。比如说对于Varnish(一个很有名的缓存服务软件),可以使用一个复制的Host header。Varnish是通过最先到达的请求的host header来辨别host的,而Apache则是看所有请求的host,Nginx则只是看最后一个请求的host。这就意味着你可以通过下面这个请求来欺骗Varnish达到污染的目的:

> GET / HTTP/1.1

> Host: example.com

> Host: evil.com

应用本身的缓存也可能受到污染。比如Joomla就将取得的host值不经html编码便写进任意页面,而它的缓存则对这些没有任何处理。比如可以通过

下面的请求来写入一个存储型的xss:

实际上的请求是这样的:

> GET / HTTP/1.1

> Host: cow"onerror=alert(1)rel=stylesheet

响应其实已经受到污染:

这时只需要浏览首页看是否有弹窗就知道缓存是否已经被污染了。

2.2密码重置中毒

一种普遍的用来实现密码重置功能的方法是:生成一个密钥令牌,并且发送一封包含着该令牌的超级链接的电子邮件。如果一个攻击者请求一个带有attacker-controlled的host头类型的密码重置,会发生什么?

如果在生成重置链接时,该web应用程序使用到这个host头的值,攻击者就可以在密码重置链接中“投毒”并发送到受害者那里。而如果受害者点开了邮件中“带毒”的重置链接,那么攻击者将能获得密码重置的令牌,进而可以重置受害者的密码了。

这个地方的漏洞是: 这一部分使用的Hostheader是来自用户重置密码的请求,那么攻击者可以通过一个受他控制的链接来污染密码重置的邮件。

当然这种攻击方式一定要能骗取用户点击访问这个受污染的链接,如果用户警觉了没有点击,那么攻击就会失败。当然你自己也可以配合一些社会工程学的方法来保证攻击的成功率。

3漏洞验证

通过抓包工具如burpsuite,获取数据包后,如果修改host头仍然能返回正确的数据,则证明该漏洞存在,如果系统返回错误的信息,则证明漏洞不存在

4安全加固建议

4.1安全的配置

假设你可以通过任何类型的应用来发起一个http请求,而host header也是可以任意编辑的。虽然在一个http请求里,host header是用来告诉webserver该请求应该转发给哪个站点,但是事实上,这个header的作用或者说风险并不止如此。

比如如果Apache接收到一个带有非法host header的请求,它会将此请求转发给在 httpd.conf 里定义的第一个虚拟主机。因此,Apache很有可能将带有任意host header的请求转发给应用。而Django已经意识到了这个缺陷,所以它建议用户另外建立一个默认的虚拟主机,用来接受这些带有非法host header的请求,以保证Django自己的应用不接受到这些请求。

不过可以通过X-Forwarded-Host 这个header就可以绕过。Django非常清楚缓存污染的风险,并且在2011年的9月份就通过默认禁用X-Forwarded-Host这个header来修复此问题。

POST /en-US/firefox/user/pwreset HTTP/1.1

X-Forwarded-Host: evil.com

即使Django给出了补丁,但是依然存在风险。Webserver允许在host header里面指定端口,但是它并不能通过端口来识别请求是对应的哪个虚拟主机。可以通过下面的方法来绕过:

这直接会导致生成一个密码重置链接:

:@passwordreset.net/users/pwreset/3f6hp/3ab-9ae3db614fc0d0d036d4

当用户点击这个链接的时候就会发现,其实这个key已经被发送到passwordreset.net这个站点了。在我报告了此问题后,Django又推出了一个补丁:

这个补丁只是简单的通过黑名单方式来简单的过滤[email protected]文本而不是html的方式发送的,所以此补丁只需要添加一个空格就可以绕过:

4.2服务器安全配置

由于http请求的特点,host header的值其实是不可信的。唯一可信的只有SERVER_NAME,这个在Apache和Nginx里可以通过设置一个虚拟机来记录所有的非法host header。在Nginx里还可以通过指定一个SERVER_NAME名单,Apache也可以通过指定一个SERVER_NAME名单并开启UseCanonicalName选项。建议两种方法同时使用。

4.3应用安全配置

减缓和应对host头的攻击,就是不要信任host头。然而在某些情况下,却是说起来容易做起来难。如果你必须使用host头作为一种识别web服务器位置的机制的话,建议要求管理员提供一个使用包含有各个被允许的主机名的白名单来进行应对。

E

N

D