Abusing HTTP Misconfigurations  

Identifying Unkeyed Parameters

In a web cache poisoning attack, we want to force the web cache to serve malicious content to other users. To do so, we need to identify unkeyed parameters that we can use to inject a malicious payload into the response. The parameter to deliver the payload must be unkeyed since keyed parameters need to be the same when the victim accesses the resource. This would mean the victim has to send the malicious payload themselves, effectively resulting in a scenario similar to reflected XSS.

As an example, consider a web application that is vulnerable to reflected XSS via the ref parameter. It also supports multiple languages in a language parameter. Let's also assume that the ref parameter is unkeyed, while the language parameter is keyed. In this case, we can deliver the payload using the URL /index.php?language=en&ref="><script>alert(1)</script>. If the web cache then stores the response, we successfully poisoned the cache, meaning all subsequent users that request the resource /index.php?language=en get served our XSS payload. If the ref parameter was keyed, the victim's request would result in a different cache key, thus the web cache would not serve the poisoned response.

Thus, the first step in identifying web cache poisoning vulnerabilities is identifying unkeyed parameters. Another important takeaway is that web cache poisoning in most cases only helps to facilitate the exploitation of other vulnerabilities that are already present in the underlying web application, such as reflected XSS or host header vulnerabilities. In some cases, however, web cache poisoning can turn un-exploitable issues in a default/plain web server setting into exploitable vulnerabilities.


Unkeyed request parameters can be identified by observing whether we were served a cached or fresh response. As discussed previously, request parameters include the path, GET parameters and HTTP headers. Determining whether a response was cached or not might be hard. In our lab, the server tells us via the X-Cache-Status header, however, in a real-world setting this may not be the case. Instead, we can achieve this manually by changing parameters and carefully observing the response. For instance, if we change a parameter value and the response remains the same, this indicates that the parameter is unkeyed and we were served the same cached response twice. Let's start with a basic example to showcase a basic cache poisoning scenario.

Unkeyed GET Parameters

Our web application is a simple site displaying some text and allowing us to embed our own text:

The web application sets the GET parameter language and our content is embedded via the content parameter. Let's investigate if either of those parameters are unkeyed. To do so, we first send an initial request with only the language parameter. The first request results in a cache miss, while the second response was cached (as indicated by the X-Cache-Status header in the response):

image

When we now send a different value in the language parameter, we can see that the response differs and we get a cache miss. Therefore, the language parameter has to be keyed:

image

We can apply the same logic to the content parameter. When we send the following series of requests, we can observe the following behavior:

  • Request to /index.php?language=test&content=HelloWorld results in a cache miss
  • The same request to /index.php?language=test&content=HelloWorld results in a cache hit
  • Request to /index.php?language=test&content=SomethingDifferent results in a cache miss again

Since the third request triggers a cache miss, we can deduce that the cache key was different from the first two requests. However, since only the content parameter is different, it has to be keyed. Therefore, both the language and content parameters are keyed, so no luck here.

A little more investigation of the web application reveals that attempting to access the admin panel and using the link to go back sets a third parameter called ref. Let's again apply our testing to find out if this parameter is keyed or unkeyed. We can observe the following behavior:

  • Request to /index.php?language=valuewedidnotusebefore&ref=test123 results in a cache miss
  • The same request to /index.php?language=valuewedidnotusebefore&ref=test123 results in a cache hit
  • Request to /index.php?language=valuewedidnotusebefore&ref=Hello also results in a cache hit

This time, the third request also triggers a cache hit. Since the ref parameter is different, however, we know that it has to be unkeyed. Now we need to find out whether we can inject a malicious payload via the ref parameter.

Before we move on, here are a few remarks on this technique of determining keyed and unkeyed parameters. This works well in our test environment, however, in a real engagement where the target site is potentially accessed by a large number of users during our testing, it is close to impossible to determine whether we received a cached response due to an unkeyed parameter or because another user's request resulted in the response being cached. We should therefore always use Cache Busters in a real-world engagement, which we will discuss in a later section.

Now, to determine whether the ref parameter is exploitable or not, we need to determine how this parameter influences the response content. We can see that its value is reflected in the submission form:

image

Since no sanitization is applied, we can easily break out of the HTML element and trigger a reflected XSS like so:

GET /index.php?language=unusedvalue&ref="><script>alert(1)</script> HTTP/1.1
Host: webcache.htb

Note: Remember to use a value for the language parameter that was never used before to avoid receiving a cached response.

This allows us to poison the cache for any user that browses the page in our targeted language. Our goal is to force an admin user to reveal the flag by requesting /admin.php?reveal_flag=1 which we are unauthorized to do. We are not going into detail about the XSS payload here but we can achieve this with a payload similar to the following. For more details, check out the Cross-Site Scripting (XSS) module here.

<script>var xhr=new XMLHttpRequest();xhr.open('GET','/admin.php?reveal_flag=1',true);xhr.withCredentials=true;xhr.send();</script>

This results in the following cache poisoning request, assuming our victim visits the site with the language=de parameter (after adding "> for XSS injection):

GET /index.php?language=de&ref=%22%3E%3Cscript%3Evar%20xhr%20=%20new%20XMLHttpRequest();xhr.open(%27GET%27,%20%27/admin.php?reveal_flag=1%27,%20true);xhr.withCredentials%20=%20true;xhr.send();%3C/script%3E HTTP/1.1
Host: webcache.htb

After poisoning the cache and waiting for a while, the admin should visit the site, trigger our XSS payload and reveal the flag for us.

Unkeyed Headers

Similarly to unkeyed GET parameters, it is quite common to find unkeyed HTTP headers that influence the response of the web server. In the current web application, assume we found the custom HTTP header X-Backend-Server which seems to be a left-over debug header that influences the location a debug script is loaded from:

image

We can apply the same methodology from before to determine that this header is unkeyed. Since this header is reflected without sanitization, we can also use it to exploit an XSS vulnerability. We can thus use the header to deliver the same payload as before with the following request:

GET /index.php?language=de HTTP/1.1
Host: webcache.htb
X-Backend-Server: testserver.htb"></script><script>var xhr=new XMLHttpRequest();xhr.open('GET','/admin.php?reveal_flag=1',true);xhr.withCredentials=true;xhr.send();//

/ 1 spawns left

Waiting to start...

Questions

Answer the question(s) below to complete this Section and earn cubes!

Click here to spawn the target system!

Target: Click here to spawn the target system!

+10 Streak pts

Previous

+10 Streak pts

Next