siunam's Website

My personal website

Home Writeups Blog Projects About E-Portfolio

Cache key injection | Jan 26, 2023

Introduction

Welcome to my another writeup! In this Portswigger Labs lab, you’ll learn: Cache key injection! Without further ado, let’s dive in.

Background

This lab contains multiple independent vulnerabilities, including cache key injection. A user regularly visits this site’s home page using Chrome.

To solve the lab, combine the vulnerabilities to execute alert(1) in the victim’s browser. Note that you will need to make use of the Pragma: x-get-cache-key header in order to solve this lab.

Exploitation

Home page:

Burp Suite HTTP history:

When we go to /, if we’re not authenticated, we’ll redirected to /login with parameter lang=en.

Also, the /login/’s parameter lang doesn’t allow to be cached.

Moreover, the Vary header is set to Origin. The Vary header specifies a list of additional headers that should be treated as part of the cache key even if they are normally unkeyed. Hence, in this case, the Origin header is keyed.

View source page:

<link rel="canonical" href='//0ad9004404e23626c2edfc4f00b100f0.web-security-academy.net/login/?lang=en'/>
[...]
<script src='/js/localize.js?lang=en&cors=0'></script>

As you can see, it has a canonical <link> element, and loaded a JavaScript called localize.js with parameter lang and cors.

localize.js:

document.cookie = 'lang=en';

This script will set a new cookie called lang.

Armed with above information, it seems like the canonical <link> and <script> element is dynamically generated.

Let’s try to change it:

As you can see, we can control those 2 elements!

We can try to inject a XSS payload:

However, our payload is being HTML encoded!

Luckly, we can still try to inject our payload in the canonical <link> element:

'/><script>alert(1)</script>

Nice! We found a reflected XSS vulnerability!

However, this reflected XSS request’s cache can’t be poisoned.

After some testing, I found that in /login the parameter utm_content from the cache key flawed regex. This allows us append arbitrary unkeyed content to the lang parameter:

/login?lang=en?utm_content=test

That being said, the import of localize.js is vulnerable to parameter pollution.

Also, we can test the Origin header:

As you can see, nothing happened when we add that header.

However, since this JavaScript has a parameter called cors, we can enable it by setting it to 1:

Then, we can use the Pragma: x-get-cache-key header to identify that the server is vulnerable to cache key injection, meaning the header injection can be triggered via a crafted URL:

By combining the above quirks, we can exploit cache key injection and HTTP request smuggling!

GET /js/localize.js?lang=en?utm_content=z&cors=1&x=1 HTTP/1.1
Origin: x%0d%0aContent-Length:%208%0d%0a%0d%0aalert(1)$$$$

This request will set the cors to 1, which allows Origin header. Then, we first set the Access-Control-Allow-Origin response header to x (This could be anything). After that, smuggle the “new line & carriage return” character (\r\n or %0d%0a in URL encoding), Content-Length to 8, and our payload.

The URL decoded smuggling response will be:

Access-Control-Allow-Origin: x
[...]
Content-Length: 8

alert(1)

GET /login?lang=en?utm_content=x%26cors=1%26x=1$$Origin=x%250d%250aContent-Length:%208%250d%250a%250d%250aalert(1)$$%23 HTTP/1.1

This request will redirects victim to a login page with a poisoned and smuggled JavaScript, which will then execute alert(1).

Let’s poison those cache!

Nice!

What we’ve learned:

  1. Cache key injection