HTTP实现导致的多重Host歧义
今天分析的这篇论文在2016年于CCS会议发表,题目是《Host of Troubles: Multiple Host Ambiguities in HTTP
Implementations》,其DOI信息在这里。
这篇文章给我耳目一新的感觉。各厂商在实现通用协议时,为了兼容性或是设计考虑不周,得到的产品往往比协议规定宽泛。在不同产品的宽泛协议实现之间,也许存在着一些非常严重的漏洞。
概述
在HTTP请求中,Host头是一个与安全密切相关的元素。它是执行安全性和缓存策略的基础。虽然目前的规范通常明确了如何解析和解释与主机相关的协议字段,但这篇文章发现这些实现是有问题的。文中测试了大量广泛使用的HTTP实现,并发现了许多不兼容和不一致的主机处理行为。当面对一个精心设计的HTTP请求(例如有多个Host头)时,两种不同的HTTP实现方式通常得到不同的接受、理解结果。文中展示了许多能够引起对HTTP实现之间主机不一致的解释的技术,以及不一致性如何导致严重的攻击,如HTTP缓存中毒和安全策略旁路。这个问题的普遍存在,突出了互联网协议的规范和实现之间的差距可能带来的负面影响。
HTTP请求
一个HTTP请求包含请求起始行、0或多个请求头与可选的消息正文。请求起始行与请求头指定了HTTP协议字段。在一个请求中,HTTP协议字段的重要作用之一是使接收者能够定位到请求的资源。在HTTP 1.0协议中,唯一一个具有此目的的是请求行中的请求目标。请求目标可以是以“/”开头的、基于主机的路径,或者是一个由模式、主机和路径组成的绝对URI。后者设计的目的在于支持透过代理的资源访问,但是终端系统也会接受请求行中的绝对URI。HTTP 1.1协议引入了Host字段,用于支持多主机环境下的请求路由选择,例如在同一IP地址上部署多个网站。这些网站被不同的域名隔离。
HTTP是一个明确支持中间媒介的C/S协议。文中分析了5类广泛配置的媒介:前向代理、拦截代理(透明缓存)、反向代理、内容分发网络(CDN)与防火墙。文中把发送请求的设备称为下游(downstream),接收请求的设备称为上游(upstream)。
这些中间媒介的一个重要且密切相关的不同点在于它们怎样处理HTTPS流量。前向代理、透明缓存与基于网络的防火墙不能检查基于HTTPS的HTTP信息,除非它们作为TLS/SSL的中间人(man-in-the-middle)。形成对比的是,对于支持HTTPS的反向代理与CDN,HTTPS连接能够终止。
多重Host歧义
一般地,处理HTTP请求可以分为两个阶段:第一是解析文字信息,以识别有效的协议字段,并将识别出的字段翻译为语义结构;第二是用语义结构进行进一步操作。具有无效字段的请求应该在第一阶段拒收,并回复4XX客户端错误信息。
在处理HTTP请求的过程中,重点之一是如何处理主机字段,因为Host是资源定位、请求路由选择、缓存等的重要协议字段。对于一个精心制作的、来自敌方的请求,当两个当事人在对一个HTTP处理链的解析和解释处理不同的时候,出现多重Host歧义。由于其语义上的重要性,两个当事人之间的不一致常常导致灾难性的后果。
文中一共对33个广泛使用的HTTP实现进行了黑盒测试,以观测多重Host歧义问题,其中包括6种服务器、2种透明缓存、3种前向代理、7种反向代理、8个CDN与9种防火墙。有的软件具有多个功能,如Squid可以配置为透明缓存、前向代理与反向代理,文中用名称(类别)进行区分。
通过检测下游-上游对(downstream-upstream pair),文中指出了3种攻击方法。下面分别介绍。
多个Host头
RFC 7230中明确指出,当存在多个Host头时,请求应被拒绝,并回复400 Bad Request。但是在测试的33个实现中,有25个没有遵守这条标准。Apache(服务器,反向代理)将这些Host头隐式地组合成一个无效的主机;OS X(防火墙)似乎也是这样。剩下的22个,除腾讯(CDN)与ESET(防火墙)采用最后一个头外,其余都采用第一个头。
空格包围的Host头
空格包围的Host头可以分为3种:第一个头前具有空格、其它头前具有空格、头后具有空格。对于第1和3种情况,RFC 2616没有明确要求。对于第2种,RFC 2616要求将其看作前一行的折叠,应将其前面的换行符去掉,并与前一行连接。 RFC 7230对于这3种情况均有明确要求。对于第1种,要求拒绝或忽略;对于第2种,虽然废弃了折叠,但为了兼容仍允许代理与服务器按折叠行处理;第3种则被禁止。
绝对URI作为请求目标
RFC 2616与RFC 7230均要求服务器接受绝对URI作为请求目标,并要求优先考虑请求目标而不是Host头。RFC 7230还要求Host头应与绝对URI的主机名相同。但是,不同的实现方式对这个要求有较大差异。除Akamai(CDN)外,所有实现均遵守RFC,优先使用绝对URI。但只有Azure(CDN)做了同一性检查。转送时,除LiteSpeed(反向代理),大多数实现将绝对URI重写为它的路径,并添加Host头。无法识别时,Lighttpd(反向代理)、Varnish(反向代理)与Fastly(CDN)直接转送绝对URI。
##攻击方式
以下介绍两种类型的攻击,每个方式有两个方式。
HTTP缓存中毒
第一种攻击方式利用了Squid(透明缓存)之间的不一致,以攻击任意未加密网站。攻击者首先与attack.com建立TCP连接,Squid作为中间代理拦截和调整了这个连接。攻击者之后将victim.com作为请求的绝对URI,并指定attack.com作为Host头。Squid识别请求是victim.com,但在检查IP地址时错误地使用了Host头,也就是attack.com。因此,代理将请求发给指定attack.com,但将回复作为victim.com的内容缓存下来。
第二种攻击方式利用了上游与下游的不一致。例如,攻击者注册了Akamai的服务,使用域名为akamai-attack.com,以攻击akamai-victim.com。攻击者生成一个恶意请求,使Squid认为请求属于akamai-victim.com,但Akamai认为属于akamai-attack.com。
过滤绕过
第一种攻击方式影响了防火墙的黑名单规则。例如,ESET的黑名单里有block.com,于是攻击者可以生成一个请求,让ESET认为其访问的是allow.com,但服务器认为是block.com,于是返回block.com的内容。
第二种攻击方式可以绕过多主机环境下的保护。例如,攻击者以域名protectdisabled.com注册CloudFlare与Fastly,以攻击protectenabled.com。protectenabled.com被CDN的安全功能保护。攻击者首先取消对protectdisabled.com的所有保护,之后生成一个具有歧义和攻击负载(如SQL注入)的请求。CloudFlare与Fastly认为请求属于不具有安全保护的protectdisabled.com,但上游认为请求属于protectenabled.com。上游信任来着CDN的请求,于是攻击成功。
总结
标准是人为制定的,难免有疏漏之处;程序是人为编写的,难免有兼容问题。这篇论文对标准的疏漏与不同HTTP实现方式之间的理解歧义进行了研究,方向十分新颖。有一句话说,“最大的漏洞就是人”。或许正是如此吧。