Intigriti's Nov XSS Challenge Writeup

This is an XSS challenge. Guess what? This is also a note-taking application like my (shameless plug) previous month's challenge. The goal of the challenge is to take over the admin's account, which has the flag in it.
Intro
This challenge is organized in a bit
differentweird way.The notes application runs on
api.challenge-1122.intigriti.io, andcdn.challenge-1122.intigriti.iois used to store the notes and profile pictures of users.Interestingly,
cdn.challenge-1122.intigriti.iouses Varnish to cache static files.Also, one of the JavaScript files contains a reference to the
staging.challange-1122.intigriti.iodomain. Interestingly enough, this staging domain also runs the same application.JSON Web Toked is used for the session. Did that ring a π? Yes, I found that the production and the staging domain use the same JWT secret to sign the token. This means that we can register
godson@0xgodson.comon both the production and staging domains, which are two totally different accounts in different environments. However, we can use the JWT token we obtain on the staging domain to access thegodson@0xgodson.comaccount in the production environment, as the same JWT secret is used to sign the token.So, technically, we can register any username on staging and use the signed JWT on
api.challenge-1122.intigriti.ioto take over any account. (Note that the admin bot will create new admin accounts every time when it visits a submitted URL. β username is also unpredictable)
Notes
Notes are stored in the pattern of
cdn.challenge-1122.intigiti.io/<username>-<uuid>.html. Here, UUID is the noteβs unique UUID.Also, the server sets a CSP header in HTTP response β
Content-Security-Policy: script-src 'none'; object-src 'none'. Very strict CSP, as it blocks any script execution.

Profile Pic Caching
- Remember that I mentioned Varnish is used? Varnish is primarily used for caching. After some enumeration, I observed that profile pictures are cached by Varnish. The following screenshot shows that profile pictures are cached and served from cache
X-Cache: HITwhen they requested more than one times:

- But what does Varnish cache? Only image files? After playing around a bit, I observed that vanish caches the files if its extension is something like
.pngor.jpg, etc.
Caching the Uncached
Remember that the server sets a CSP header to prevent script execution? The CSP header is only set when it returns valid content. When an HTTP request is sent to a non-existing endpoint, the CSP header is not attached to it.
Interestingly, the application does not return a 404 status when an HTTP request is sent to a non-existing endpoint. It sets the HTTP status to 200 but includes an error message indicating the 404 page. Similarly, it does not attach the CSP header. The following screenshot shows an HTTP request that was sent a couple of times to the server so that Varnish can cache it. Varnish caches it because it ends with
.png, thinking that itβs a static file. It can be observed that the status code is 200, does not include the CSP header, and user input (from the URL path) is reflected in the HTTP response. More than that, the content type is set totext/htmlπ.

- Since the URL is reflected in the HTTP response, it can be abused for XSS with the help of Varnish. For example, the following HTTP request is issued twice to cache a simple
alert. From the HTTP response header, it can be observed that Varnish has cached it because the request path ends with.png.

- Visiting the URL in the browser gives us a sweet popup π:

You could ask why we need to abuse Varnish for XSS when we already can directly exploit this like a reflected XSS. We need to do this way because the browser would auto URL encode characters like
<or>, and when the server receives it, it will not URL decode it. So, even if we inject an XSS payload, the response will contain as if it is URL encoded, which the browser actually encodes.But, with a Proxy tool like Burp Suite, we can send a payload by having it automatically URL encoded, ensuring that the server receives it as it is and not as URL encoded.
So, we are using Burp Suite to cache the payload and visiting the browser to get the cached response. Itβs valid to think the server would still receive a URL-encoded payload when we try to access the malicious link. Yes, thatβs right, but Varnish is our guy π. It decodes it for us, finds the cache key, and returns the cached response (the XSS payload stored here) without forwarding the request to the server.
Final Exploit
I found two possible solutions.
Use the above XSS to register a service worker on
cdn.challenge-1122.intigriti.ioto cache all requested pages.Use the XSS to redirect the Admin bot to
api.challenge-1122.intigriti.io, and when this page loads, notes created by the admin bot will be loaded into the page. Subsequently, the service worker can be used to cache the notes page. Then, the server worker will send us the URL so we can get the post UUID and adminβs username randomly generated.Another solution: With the XSS in
cdn.challenge-1122.intigriti.io, we could callwindow.open("https://api.challenge-1122.intigriti.io")and when theapi.challenge-1122.intigriti.ioloads, it will load all posts created by the user (admin bot is the user here β It creates a post with the flag in it) by framing thecdn.challenge-1122.intigriti.io/<username>-<uuid>.html.- Here,
api.challengeis a child andcdn.challengeis the parent window. So, it is possible to read the frames src fromcdn.challengeonapi.challengeif the frame-src is the same origin ascdn.challenge, as shown in the following screenshot:
- Here,

- After leaking the iframe link (note URL β includes the username and the post UUID), we can find the username of the admin, we can create an account on
stagingdomain with that username to get signed JWT for that username. Then it can be used onapi.challange-1122.intigriti.ioto the takeover admin account in the production. Once I done this, I found that flag was not in the post, but in adminβs profile picture. So, after taking over the account, I was able to see the profile picture:



