Siunam's Website

My personal website

Home About Blog Writeups Projects E-Portfolio

Internal cache poisoning | Jan 26, 2023

Introduction

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

Background

This lab is vulnerable to web cache poisoning. It uses multiple layers of caching. A user regularly visits this site’s home page using Chrome.

To solve the lab, poison the internal cache so that the home page executes alert(document.cookie) in the victim’s browser.

Exploitation

Home page:

View source page:

<link rel="canonical" href='//0a0a00a404705384c0c007fd008000da.web-security-academy.net/'/>
[...]
<script type="text/javascript" src="//0a0a00a404705384c0c007fd008000da.web-security-academy.net/resources/js/analytics.js"></script>
<script src=//0a0a00a404705384c0c007fd008000da.web-security-academy.net/js/geolocate.js?callback=loadCountry></script>
<script>
    trackingID='bRsp6pn2AnfToVhm'
</script>

As you can see, it has a canonical <link> element, and loaded 2 JavaScript files: analytics.js, geolocate.js.

analytics.js:

function randomString(length, chars) {
    var result = '';
    for (var i = length; i > 0; --i) result += chars[Math.floor(Math.random() * chars.length)];
    return result;
}
var id = randomString(16, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
fetch('/analytics?id=' + id)

geolocate.js:

const setCountryCookie = (country) => { document.cookie = 'country=' + country; };
const setLangCookie = (lang) => { document.cookie = 'lang=' + lang; };
loadCountry({"country":"United Kingdom"});

Also, it seems like the src and href are dynamically generated?

Let’s use a HTTP header called X-Forwarded-Host to change the host value:

In here, we can control the host of analytics.js.

Also, we can add a GET parameter in /, and the canonical <link> element is reflected:

That being said, we can control the host of both elements, and GET parameter in canonical <link> element.

Now, we can keep trying to send the request with the X-Forwarded-Host header, and something weird will happened:

When I changed a different GET parameter value, the imported geolocate.js JavaScript host also changed!

This indicates that this fragment is being cached separately by the internal cache. Notice that we’ve been getting a cache hit for this fragment even with the cache-buster query parameter - the query string is unkeyed by the internal cache.

Let’s get a cache hit, and try to remove the X-Forwarded-Host header:

Hmm… The internally cached fragment still reflects our exploit server URL, but the other two URLs don’t. This indicates that the header is unkeyed by the internal cache but keyed by the external one.

Therefore, we can poison the internally cached fragment using this header.

To exploit this internal cache poisoning, we can:

To do so, remove the cache buster, and add back the X-Forwarded-Host. Then keep sending the request until we hit the cache:

What we’ve learned:

  1. Internal cache poisoning