Log Action
Writeup for Log Action (Web) - UIU CTF (2024) 💜
Last updated
Writeup for Log Action (Web) - UIU CTF (2024) 💜
Last updated
I keep trying to log in, but it's not working :'(
The source code is available for download, so I first spun up a local instance of the challenge with docker-compose up
.
The homepage redirects to a login form. The password must be at least 10 characters long, but the validation is client-side, so you can send whatever you like with burp. Still, common credentials like admin:admin
are rejected with a "something went wrong" error.
It looks like we've got a Next.js application, but rather than digging through the obfuscated JS in the browser, best to review the source code 🔎
The backend
simply holds a flag.txt
file - does a vulnerability class pop into your mind already? 👀
The frontend
has quite a bit of code, so I'll highlight some significant parts. First is the auth.ts
. Notice that the admin password is randomised for each login attempt 🧐
There's an /admin
endpoint, although the tsx
file does nothing other than display "You logged in as admin."
An auth.config.ts
has some logic surrounding the admin authentication.
Finally, actions.ts
contains some more code relating to the authentication.
At this stage, nothing stands out to me as a potential vulnerability. The next step is to check package.json
and see if there are any vulnerable dependencies.
Can you guess which one I'm going to look into? That's right, the only one with a fixed version; next
. All the other libraries are set to use the latest available version, so if one of those had a vulnerability that the challenge authors intended to include, the challenge would break as soon as it's patched.
I proceeded to Google next 14.1.0 exploit
, and one of the first results is from Snyk, highlighting an SSRF vulnerability (CVE-2024-34351
) that is present in next
versions <14.1.1
.
So, the SSRF is patched in the very next release after this one - what a coincidence 😆 Maybe this is the vulnerability class you were thinking about earlier?
Assetnote discovered the vulnerability, and they released an excellent blog post detailing the discovery (including PoC), which I highly recommend reading in its entirety.
The first part covers an SSRF vuln in the _next/image
component, which is interesting but irrelevant to this challenge. However, the second section focuses on "SSRF in Server Actions" - remember we saw an actions.ts
file? 💡
I won't cover server actions
or dive into the affected code (read the blog!!). Instead, I'll try to stick to the practical steps (TLDR).
If a server action responds with a redirect starting with /
(e.g., a redirect to /login
), the server will fetch the result of the redirect server side and return it to the client.
However, the Host header is taken from the client.
Therefore, if we set a host header to an internal host, NextJS will fetch the response from that host instead of the app itself (SSRF).
I checked through the source code again, looking for valid redirects:
redirect("/admin")
in actions.ts
and auth.config.js
, but it's only triggered after a successful login.
redirect("/login")
in page.tsx
(logout), which looks promising!
Fortunately, we don't need to be logged in to access the /logout
endpoint 🙏
Therefore, we can intercept the request and insert our ATTACKER_SERVER
domain as the Host
and Origin
header values. Note that the URL doesn't matter since the Next-Action
header value is used to identify the action.
The ATTACKER_SERVER
gets a hit ✅
We need to exfiltrate data, so let's follow the remainder of the blog post:
Set up a server that takes requests on any path.
On any HEAD request, return a 200 with Content-Type: text/x-component
.
On a GET request, return a 302 to our intended SSRF target.
When NextJS fetches from our server, it will satisfy the preflight check on our HEAD request but will follow the redirect on GET, giving us a full read SSRF!
Putting it all together, we modify the supplied PoC.
So, we will respond to the initial HEAD
request with a 200 OK
of content-type text/x-component
, which triggers a GET
request to our server. At this point, we issue a 302
redirect to the flag.txt file on the backend
.
That's it - let's serve the PoC using deno
.
Reissue the request in burp and ensure the requests line up as expected in our server log.
They do, and we receive the fake flag 😊
All that's left is to repeat the exploit against the remote server and receive the real flag 😏
Flag: uiuctf{close_enough_nextjs_server_actions_welcome_back_php}