在本文中,我们将探讨何谓HTTP响应拆分以及攻击行为是怎样进行的。一旦彻底理解了其发生原理(该原理往往被人所误解),我们就可以探究如何利用响应拆分执行跨站点脚本(简称XSS)。接下来自然就是讨论如果目标网站存在响应拆分漏洞,我们要如何利用这一机会组织CSRF(即跨站点伪造请求)攻击。最后,我们一起来看看哪些预防措施能够抵御这些攻击行为。如果大家对这个话题感兴趣,不妨继续读下去。
什么是HTTP响应拆分?
首先让我们设想一下某个具备多种语言选项的页面。该页面的默认语言为英语,但其中同时具备一个下拉菜单,允许我们在选定其中对应的其它语种后,整个页面的语言也同时发生切换。比方说根据初始页面的配置,302重新指向的结果为http://www.abc.com/index.php?lang=en。但对于来自德国的用户而言,当然希望页面内容以德语呈现,这时他们就可以从下拉菜单的备用语言中进行选择。这使得302重新指向将被发往服务器上的德语页面——http://www.abc.com/index.php?lang=german。用户的浏览器会遵循重新指向的引导,并将德语页面正常呈现出来。
现在让我们思考一下HTTP 302重新指向响应的主体内容。内容大体如下:
- HTTP/1.1 302 Moved Temporarily
- Location: http://www.abc.com/index.php?lang=en
或者是:
- HTTP/1.1 302 Moved Temporarily
- Location: http://www.abc.com/index.php?lang=german
大家可能已经发现了,惟一产生变化的只有lang参数的值。也就是说,这个值是由用户所控制,我们可以将该值设置为任何想要的内容。正是这种特性导致了HTTP响应拆分攻击的发生。
此时我们不再把参数值设定为”german”,而是按照下列内容进行设定:
- a) The value ‘german’
- b) CR/LF – %0d%0a
- c) A response with Content Length 0 [这里之所以设长度为0,是因为这一段其实无关紧要]
- d) CR/LF – %0d%0a
- e) A response which contains malicious content [举例来说,可以设定JavaScript会在页面被访问时自动下载恶意软件]
先来看看c)的内容–这也是首个响应。HTTP协议的工作方式是一个请求对应一个响应,因此针对该请求——即http://www.abc.com/index.php?lang=german的响应是经过精心设计的。其实我们并不关心这个响应本身及其内容,我们想要的只是将Content-Length: 0设为其响应头。
CR/LF(即回车换行符)是响应之间的分界符。所以只要我们如d)中所示加入CR/LF内容,那么第二轮响应即会启动,且根据HTTP协议的规定这是完全正常的。在新一轮响应中我们可以添加大量信息。举例来说,如果我们打算显示一条”Hello, you have been phished”(意为’你好,你已然中招了’)的消息,此时面前已经完全没有任何阻碍了。只需输入如下所示的内容,即可轻松实现:
HTTP/1.1 200 OK Content-Type: text/html Content-Length: 41 Hello, you have been phished
还是觉得有点迷糊?让我们再总结一次。攻击者控制参数并发送一个将产生两次响应的请求;这两次响应都由攻击者组织,并以服务器为目标。首个响应旨在回应将页面转化为德语的请求,而第二个响应(到目前为止)还未经解释,它只是暂时挂起–因为该响应还没有能够映射的对应请求。请记住,HTTP需要响应(无论内容代码是什么),但它需要的是一个能对应所有请求的响应。因此挂起中的HTTP响应是无法工作的。
现在请仔细阅读…因为这部分正是大多数人(连我自己在很长一段时间内也是如此)没有搞清楚的内容。为了处理第二个挂起中的响应,攻击者会迅速对服务器上的页面发送一条有效的公开访问(通常是这样)请求,比如说branches.html。
这里假设他发送的请求为:
GET /branches.html HTTP/1.1 Host: www.abc.com
就在发出首个包含”可完全自定内容”参数的请求之后,他会旋即发出上述第二条请求,而这也正是”Hello, you have been phished”字段的映射对象。两条请求对应两次响应,大家明白了吗?
哈哈,恐怕各位还是有些困惑。尽管大家可能已经了解整个映射的发生过程,但仍然没闹明白这种攻击是如何影响其他人的。毕竟,攻击者的这一切行动都发生在他自己的计算机上,所修改的请求也只限于他个人…也就是说,会受影响的只有他自己。说实话,为什么攻击者要对自身展开攻势?这似乎毫无道理可言啊。不过我要指出的是,这对位于中间帧及缓存中的代理服务器或者某些设备而言,可以说是接纳请求及响应的关键性一环。
攻击者必须躲在代理服务器之后,并借助代理将他的请求发送到互联网上的目标服务器处。如果他想感染其它用户,这些被害用户也必须处于同样的代理服务器之后。因此,让我们再来总结一次(请保持耐性…)
a)攻击者发送一条包含一个值及两次响应的请求,使用%0d%0a进行分隔。在本文的例子中,请求的内容如下:
http://www.abc.com/index.php?lang=german%0d%0aContent- Length:%200%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2041%0d%0aHello, you have been phished
b)该请求的发送目的地为www.abc.com…不过重要的是,它是通过中间代理服务器进行传递的。因此现在在代理服务器上,第一个请求被映射在第一次响应上,而第二次响应则由于没有能够匹配的请求而处于挂起状态。
c)在首个请求发出后,攻击者会立即向目标网站(同样通过代理服务器)发出新请求(第二个请求),内容如下:
GET /branches.html HTTP/1.1 Host: www.abc.com
d)代理服务器会在收到branches.html后,第一时间将其映射至第二次响应中(即’You have been phished ‘)。因此接下来发往branches.html的请求将不再显示银行的分支机构名单,而是指向恶意网页。没错,对于每位访问者毕竟是如此,而不仅仅针对攻击者。为什么会这样?因为这正是缓存代理服务器的处理方式…常发请求缓存响应。也就是说,如果某个发往branches.html的请求始终产生同一份关于银行支行信息的静态列表,那么代理服务器几乎肯定会调用缓存对该请求进行响应。换言之,下一次指向branches.html的请求将自动返回来自缓存的响应。而在攻击者的安排下,代理服务器的缓存内容遭受感染,并被迫返回恶意响应而非原本的静态列表……这种状况将持续下去直到缓存过期。
希望经过上面的详细介绍,大家会对HTTP响应拆分有一个明确的概念。这里是文章的重中之重,所以不妨多读几遍。关键在于,攻击者发出的第二条请求会迫使代理服务器遵从第二条请求对应第二次响应的映射模式。一旦理解了这一点,整个概念应该就比较清晰了。
通过响应拆分实现跨站点脚本
在这里需要指出,我并不打算详细解释跨站点脚本及其具体类型。网上可以轻易找到大量讨论这方面话题的文章(我会在结尾处提供一些参考链接),通过阅读大家完全能够透彻理解这一概念。
现在我们已经对响应拆分相当熟悉,那么接下来该从攻击者的视角出发,以获得进一步的收获。我们能够通过响应拆分在目标计算机上运行JavaScript,并尝试且最终获取对其浏览器的完全控制权吗?答案是肯定的,只要对我们此前所给出的例子进行些许扩展,这一目标就可以顺利实现。
大家应该还记得,我们在前文中所设计的第二轮恶意响应只是利用简单的页面告知用户”你已然中招了”。而要实现更邪恶的目的,我们要编写一些JavaScript代码来代替原本的简单页面。与仅仅能够实现显示功能的页面不同,JavaScript能够切实运行于用户的浏览器之中。而攻击者对于目标用户浏览器的控制能力完全取决于JavaScript代码的内容。因此,如果要对前文中所设计的例子加以扩展,攻击者会设计出如下的URL:
http://www.abc.com/index.php?lang=german%0d%0aContent- Length:%200%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent- Length:%20%0d%0aalert('在你的计算机上运行JavaScript')
然后他会像以前那样向branches.html发送请求,该包含恶意JavaScript内容的请求自然会被映射至第二轮响应中。代理缓存将同原先一样受到感染,而使用着同一款代理服务器的其他用户在访问branches.html时,该JavaScript代码就会侵入他的计算机。
如果目标站点在XSS方面存在漏洞,那么我们完全可以用同样的逻辑对其展开攻击。只是在这种情况下,漏洞参数才是脚本的主体,因此我们需要利用其替换掉前一个例子中的JavaScript部分。
在我所举的例子中,JavaScript内容非常直观,结果也只是弹出一个小小的警告框。事实上JavaScript完全可以编写得更为复杂,并使攻击者获得可以完全掌控目标用户浏览器并最终控制计算机的能力。一款名为BeeF的开发框架能够为攻击者帮上大忙,只需编写一小段JavaScript再将其导入BeeF控制器,受害者的计算机就在劫难逃了。
通过响应拆分实现跨站点请求伪造
与前面一样,我不打算在这里详述CSRF攻击的来龙去脉。大家还是从文末的参考资料汇总那里去获得相关的细节信息吧。简单来说,CSRF攻击的受害者会在不知情的状态下执行某些”隐性”操作。需要强调的是,这些操作基本上都是受害者打死也不想去执行的类型。
作为CSRF攻击发生的先决条件,受害人需要首先登录到操作执行的站点上。因此如果攻击者打算诱导受害者执行的操作是”删除我的谷歌个人资料”,那么用户必须要先登录到相应的谷歌系统中,操作才能顺利执行。另外,攻击者必须能够对包括参数值在内的确切结构进行预测。以银行转账操作为例,具体数额必须准确有效,如:GET /transfer/php?acc1=1000&acc2=2000&amt=900。这样只要用户在登录至www.abc.com之后发出类似的GET指令,由acc1指向acc2的转账行为将自动执行。
在CSRF攻击当中,攻击者总会使出某种花招诱导用户点击链接,或是采取社交角度的心理战术、或是让用户访问某个处于攻击者控制之下的页面;总之尽管理论上用户是自主发出的请求,但实际上后台到底执行了什么内容受害者并不知情。
现在,让我们回到响应拆分这一话题。大家应该还记得攻击者借助响应拆分特性感染branches.html页面,并向其中注入JavaScript恶意内容以尝试在用户的浏览器上运行脚本的做法吧。而在CSRF攻击中,我们需要确保行为(例如前面提到的转账操作URL)在用户访问被感染页面时能够自动执行。换句话说,branches.html中会包含一个小型镜像,并随同该页面一同被载入。该镜像的<IMG SRC>标签将向www.abc.com网站的服务器发送内容为/transfer/php?acc1=1000&acc2=2000&amt=900的请求。
因此在执行响应拆分攻击时,整个恶意URL的内容如下所示:
http://www.abc.com/index.php?lang=german%0d%0aContent- Length:%200%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent- Length:%20%0d%0a< / body>< / html>
最终的结果是,每当用户通过代理访问受感染的页面(即branches.html),且在另一个浏览器选项卡中登录了www.abc.com站点,那么后台即会顺利地将款项转汇至攻击者指定的账户中。
示例代码:
现在让我们快速浏览一遍PHP中那些存在响应拆分漏洞的代码类型。对于初学者来说,我们以一个小型HTML文件为例(名称为respsplit1.html),其中包含了允许用户选择语言种类的下拉菜单。具体内容如下:
- <HTML>
- <BODY>
- <FORM NAME=“form” action=“respsplit1.php” method=“GET”>
- <select name=“lang”>
- <option value=“EN”>English</option>
- <option value=“GER”>German</option>
- </select>
- <INPUT TYPE=“submit” name=“Submit” value=Submit></INPUT>
- </FORM>
- </BODY>
- </HTML>
选择想要的语言类型后,大家当然会点击提交按钮,而这一输入行为将被提交至名为respsplit1.php的PHP文件处。上述所有代码的功能是截获我们的输入内容,并利用其建立一个用于重新指向的URL,再将其发送至302重新指向响应处。Respsplit1.php文件的内容如下所示:
- <?php
- $lang = $_GET[‘lang’]
- header(“Location: http://localhost/respsplit2.php?lang=$lang”);
- ?>
在浏览器中打开该HTML文件并将其置于Burp或是任何其它类型的代理编辑器中。这时请留意我们在头一个页面点击提交后所产生的响应。大家会清楚地发现,自己在下拉菜单中选择的项目实际上是本机302响应中的响应头。因此如果各位想对此进行修改,那就必须得在请求到达服务器端之前,对lang参数的值利用恶意字符串进行编辑(也就是达到我们前面所说的一项请求两次响应)。
respsplit2.php所做的仅仅是将大家所选择的语言种类显示出来,除此无它。
- <?php
- $a=$_GET[‘lang’];
- if (strcmp($a,‘EN’) == 0)
- echo “Language selected is English”;
- elseif (strcmp($a,‘GER’) == 0)
- echo “Language selected is German”;
- else
- echo “No valid language selected “;
- ?>
现在当我打算进行上试测试时,我自己的PHP框架会从响应头中把%0d%0a字符串除去,这种默认设置实际上起到了保护作用。不过如果大家所使用的是版本较老的框架,那么默认设置将不会实施保护,进而导致代码极易在CRLF处发生问题。这里所提到的过滤功能如下图所示:
- <?php
- $pattern1 = “/\%0d/”;
- $pattern2 = “/\%0a/”;
- $lang = $_GET[‘lang’];
- $r = preg_match($pattern1 , $lang);
- $s = preg_match($pattern2 , $lang);
- if (($r > 0) || ($s > 0)){
- echo ‘Carriage Return found in user input’;
- echo “<BR>”;
- }
- else {
- header(“Location: http://localhost/respsplit2.php?lang=$lang”);
- }
- ?>
我们可以自主编写更为高效的过滤器,例如将内容只包含字母及数字的项目列入白名单,并直接屏蔽掉其它一切类型;但这仅仅是防御性代码的一种使用范例。如果大家忘记编写这类过滤器,那么就等于将自己的命运完全交付给所使用的框架本身。如果各位与我使用的框架相似(Ubuntu 10.04内的APT库)还好,因为其中内置了保护机制…否则就等着为此而付出惨重代价吧。
目前已经被发现了为数不少的响应拆分类漏洞。我在文末的参考链接中提供了详细的清单。
应对措施:
◆响应拆分攻击:使用服务器端验证机制,并禁止全部用户在任何与响应头有关的输入请求中使用回车换行符(即CRLF)。
◆XSS攻击:引入白名单、黑名单过滤机制(输入验证)以及Escape HTML(输出验证)。
◆CSRF攻击:使用AntiCsrf语言符号,这样攻击者就无法准确预测目标结构,自然也不能对其加以伪造了。
结论:
响应拆分式攻击只能在多个用户使用相同代理服务器连接不同网站时奏效。代理服务器的缓存一旦受到感染,用户就会在从代理服务器缓存中读取该页面时遭到攻击。不过请注意,并非所有代理服务器都在响应拆分方面存在漏洞,虽然这算是句题外话。如果大家有兴趣进一步了解这种攻击,我强烈建议大家阅读由Amit Klein撰写的精彩论文(详见参考文献1)。
参考文献:
◆响应拆分攻击- http://packetstormsecurity.org/papers/general/whitepaper_httpresponse.pdf
◆XSS攻击 – http://www.technicalinfo.net/papers/CSS.html
◆XSS攻击应对措施-https://www.owasp.org/index.php/XSS_%28Cross_Site_Scripting%29_Prevention_Cheat_Sheet
◆CSRF攻击 – https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29
◆CSRF攻击应对措施- https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet
◆B浏览器开发框架 – http://beefproject.com/
◆学习PHP(教程)- http://www.w3schools.com/php/
◆PHP函数 – http://php.net/quickref.php
◆已曝光的响应拆分漏洞汇总清单- http://cwe.mitre.org/data/definitions/113.html
原文链接:http://resources.infosecinstitute.com/http-response-splitting-attack/