Skip to content

React2Shell Pwned Me — And It Was My Fault

12/7/2025Cybersecurity & Quality Assurance4 min read

The Monero miner didn't even knock. I was sitting there thinking my Next.js side project was the pinnacle of modern DX, and then some bot in a data center halfway across the world decided my CPU belonged to them. It’s always the same story—you chase the "shiny" and forget that the "shiny" is usually just unpolished glass with jagged edges. The culprit was React2Shell (CVE-2025-55182), which is basically what happens when you let a server deserialize binary streams without a sandbox.

The actual problem with Flight protocol

The React Server Components (RSC) implementation in Next.js 16 uses this Flight protocol to stream UI chunks. It’s snappy. It feels like magic. But the deserialization logic is a disaster. If you send a poisoned payload to /_next/flight, the server just starts building JavaScript objects out of thin air. It doesn’t ask questions. It doesn’t check if those objects are executable code. And since I was running the Node process as root—don’t look at me like that, I wanted to skip the chown headaches during a midnight deploy—the attacker didn't just get a shell. They got the keys to the kingdom.

Infrastructure for idiots

I had this Ubuntu VPS running a standard Node runtime. I thought I was being clever by using RSC to avoid the usual client-side waterfall fetching. But I essentially built a wide-open processing terminal for anyone who knew how to craft a malformed HTTP request. (And to be honest, it’s not even that hard to craft once the CVE is public). The attacker’s script was pretty standard stuff. They used curl to pull down a tarball, unpacked it in /tmp, and ran a loader script that I’m assuming was named something stupid like sex.sh based on the logs. It dropped a version of xmrig and immediately set up persistence.

Persistence is a nightmare

Look, the way these bots hide is actually kind of impressive in a depressing way. They didn't just run the binary. They renamed the miner to ntpclient and tucked it away in /root/.systemd-utils/. They even set up @reboot cronjobs to make sure the miner came back to life every time the system bounced. The audacity to mimic legitimate system daemons while eating up ninety-five percent of my CPU cycles is what really gets me. It shows you how the "messy reality" of production is always one step behind the people who spend their lives looking for deserialization bugs. I had binaries sitting in /etc/rondo, which isn't even a real directory for anything I installed, yet it looked just "official" enough to ignore if you were skimming the file system.

But the real failure isn't the code. It’s the culture of "deploy now, secure later." I was running as root because I was lazy. I didn't containerize because I thought it was overkill for a small project like Kuray.dev. And the result was a full system wipe. You can't trust a box once someone has had root-level RCE on it for three days. You just burn it down and start over.

Actually, never mind the "why," the point is that the frontend world is moving way faster than the people building the underlying security layers. We’re shipping features that turn servers into executable playgrounds. And if you’re still running your Node apps without a dedicated, low-privilege user, you’re basically just hosting a free compute cluster for the Monero network.

Hardening the fallout

I had to kill the box entirely. There’s no "cleaning" a root compromise. I reinstalled the OS, set up a dedicated nodeuser with zero permissions outside the app directory, and threw a WAF in front of those RSC endpoints. I also moved the whole thing into a container, which I should have done from the start. It’s funny how a few hours of "saving time" results in forty-eight hours of cleaning up digital blood. If you're using Next.js and RSC right now, go check your process owner. If it says root, you’re already cooked.

Related Articles

Same Category

Comments (0)

Newsletter

Stay updated! Get all the latest and greatest posts delivered straight to your inbox