Party Cat0%

FullStackDisaster

Omar Mohamed
Thanks for sharing!

بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

Full Stack Disaster
Well well.. another challenge from Mr Mushroom. This one has plenty of stuff you are gonna chain together to eventually get an RCE!
This writeup might me long so.. let's start right away. Download challenge from here
Solver from here

Source code

Let's start by our entry point: index.js. The noticeable thing is the csp headers:
Which in short means:
  • Only allow resources from the same origin (self) and cdnjs.cloudflare.com
  • Allow eval (unsafe-eval)
This might limit us a bit if there is an XSS. We will see.

Next our routes:

Auth routes are pretty standard, the only thing to mention is that after login you get an extra csrf_token cookie.

For the paste routes where you can create pastes (notes) there is a heavy blacklist middleware:
After some analysis, you can tell it only allows a very limited set of HTML tags including <script> tag..?? but not allowing me to write inbetween <script> and </script>. I can use <script src=...> though, I can use any attributes in the allowed tags.
This is interesting. So will I need to host my payload somewhere and load it? but what about the CSP? We will see shortly.

Report routes are pretty standard as well, you just report a paste by ID for the admin to review.

Upload routes are only limited to admin with some filters and logic inside, so we will keep it after we deal with this XSS first.

In utils/bot.js the bot logs in as admin, deletes csrf_token and visits the reported paste:
The cookie is httpOnly

In the codebase root there is nginx.conf file:
We can see the /report route is only allowed from localhost. This is a crucial endpoint for us to deliver the XSS payload to the admin, so we need to find a way to access it.

First Bug: Nginx Misconfiguration

If you do a little bit of search about nginx misconfigurations, you can find out the issue is in the /api location. It proxies to /auth/. The issue is with the trailing slash, it must have been /api/ to proxy correctly.
Quick Explanation: When a request is made to /api/something, nginx will strip the /api part and forward the request to http://app:3000/auth//something (notice the double slash). If you pass /apisomething nginx will strip the /api part and forward to http://app:3000/auth/something. Got it? We can traverse back to the root by using /api../, it will resolve to /auth/../ which is /. And like that we can access any route we want including /report.
Nginx misconfigiration
Check out this article for more info.

Second Bug: XSS & CSP Bypass

Now we can access the /report route, but how do we deliver the XSS payload to the admin? We need to create a paste with our payload and report it.
We know that we can use <script src=...> to load an external script, but the CSP only allows self and cdnjs.cloudflare.com. We can't host our payload on either of those. But wait, what if we can upload our payload to cdnjs.cloudflare.com?
After some searching you will find out you can indeed host your scrpts on some CDNs like jsDelivr, but you can't on CloudFlare CDN. Let's check the blacklist one more time.
<div> tag is allowed, and it allows any attributes. So is there any way we can execute js from a <div> attribute? There are frameworks that allow this, like AngularJS and AlpineJS. What about if we can load one of those frameworks from cdnjs.cloudflare.com and use it to execute our payload?
We can use AlpineJS for this, it is available on cdnjs.cloudflare.com. We can use x-init attribute to execute our payload. Let's create a simple payload to test this:
XSS PoC
Amazing! We have got our XSS...
Now what?

If the cookie was not httpOnly we could have stolen it. You are wondering How would you do that with this strict csp?
If you have a non-httpOnly cookie with a strict csp, you can make the admin add a note of his cookies using your cookies, and you simply see the note on your profile. Example payload:

Now back to the challenge, since we can't steal the cookie, we can try to make the admin do something for us on the app (remember we only can interact with the app, we can't make him do any externals because of the csp), we still have this upload functionality that only admin can access. Let's check it out.

Third Bug: File Upload & Path Traversal

upload.controller.js:
Let's analyze this filters: first it removes any ../ from the filename, then it checks if the filename ends with .jpg, .jpeg or .png (case insensitive). Finally it limits the filename to 20 characters.
We have a path traversal protection, but we notice .normalize("NFKC") is used. This function normalizes unicode characters, so we can use some unicode tricks to bypass the filters.
using (U+ff0f) nstead of / we can bypass the path traversal since the normalization is happening after the removal of ../. You can access different encoding like this from here (Download it and open it with a browser).
Cool! Now we can upload a file to any path we want. But what about the extension check? We noticed it truncates the filename to 20 characters, so we can build our file name to be exactly 20 characters then add .jpg at the end so it gets truncated. Example:
Now we can upload a file to any path we want with the name we want!

Let's try that! Login as admin on your local instance and try to upload a test image, inspect the request in burp:
Upload Request
Upload Request
We successfully uploaded a file to another path! What can we do with this?
In views/ there are some ejs templates, we can overwrite one of those templates and get an SSTI > RCE!
Views index.ejs upload
Overwritten index.ejs successfully! Notice I used /// in the payload to reach this 20 chacracters limit.
index.ejs overwritten
Now for our SSTI payload, I used:
SSTI PoC
And here we have it! RCE achieved!

It is not done yet, we implemented that as an admin, but we need the admin to do it for us.
We will utilize the XSS to make the admin upload a file for us.
But.. it is csrf protected:
CSRF Middleware:
It is a simple md5 hash of username:SALT. We know the username is admin but the SALT is REDACTED. Without overcomplicating things, if you tried to bruteforce it you will find out it is mushrooms.
Just register a user (call it test), login and get your csrf token from the cookie, then brute force the salt until you get the same token. At the end the salt is mushrooms.
Now forge the csrf token for admin and make the XSS payload to upload our malicious ejs file:
Add this to a paste, report it using the /api../report nginx misconfigiration and wait for the admin to visit it.
Final
And here you have it, you can get the flag from config/constants.js file. Download the solver script from here.

This challenge had a bit of everything. The main goal of the challenge is to learn how to chain multiple vulns together in one attack. I know you can simply exploit each one separated, but to chain them is the tricky part! I hope you enjoyed it as much as I did while writing it. Thank you for reading!
I am open for any challenges creation requests, you can reach me on X/Twitter @MushroomWasp or email me at MushroomWasp@gmail.com!
See you in the next one!

You might also like