How I Accidentally Joined a Botnet: A React2Shell Post-Mortem

I ignored a security warning to focus on finals. Two weeks later, my server was part of a Romanian spam botnet. Here is the post-mortem of how CVE-2025-55182 took me down and what I've learnt from this.

How I Accidentally Joined a Botnet: A React2Shell Post-Mortem
What is a Botnet (IoT Botnet)? | Glossary | A10 Networks

When the React2Shell (CVE-2025-55182) vulnerability was first discovered, I was in the middle of the busiest time of my semester. I was prepping for finals and simultaneously packing my bags for an overseas exchange program in Hanoi.

I actually saw the emails warning me to upgrade my Next.js and React versions. But like many of us do during crunch time, I thought, "I'll handle this later." I wasn't really aware of the potential damage this specific vulnerability could do, so I prioritized my exams and my flight.

Halfway through my trip in Hanoi, I started getting alerts from BetterStack that my server was down. I was busy with the exchange program—exploring the city, meeting people—so I just assumed it was a random glitch or a service crashing. I didn't look into it.

It wasn't until I got a much more serious email from Contabo (my server provider) that I realized something was wrong. They flagged my server for extremely high usage on Port 25 (SMTP)—a port I hadn't explicitly opened. Basically, my server wasn't just down. It was busy sending spam.

I tried to SSH in to investigate, but I was locked out. The attacker had blocked my access. I had to use the VNC console (a remote desktop tool provided by the host) to physically see what was happening on the machine. That’s when I realized I had been hacked thoroughly. My server was acting as a node in a spam botnet based in Romania.

After digging through the logs (and getting some help), here is the breakdown of exactly what happened:

  • Setup Error: My frontend was running on bare metal. I hadn't containerized it with Docker, so the Node.js process was running directly on the OS with root privileges.
  • Exploit: The attacker used the React2Shell vulnerability. They sent a malformed POST request to my Next.js server. Even though I wasn't running any complex backend services, the Next.js runtime itself was vulnerable. The request tricked the server into executing functions (similar to how eval() works) effectively giving them remote code execution.
  • Payload: The exploit downloaded a Python script (app.py) that turned my server into a spam relay.
  • Stealth: The malware was clever. It ran entirely in RAM (memory) and deleted the app.py file from the disk immediately after starting. This meant there were no files to find or reverse engineer—it was "fileless" malware.

I realized I couldn't "clean" a server that had been rooted this deeply. I immediately:

  • Nuked the server: Reinstalled the OS from scratch.
  • Rotated everything: Changed all my API keys, database passwords, and environment credentials immediately.

Going forward, I've made some major changes to my setup:

  • Dockerize Everything: My frontend is now in a container. Even if it gets hacked, the attacker is trapped inside the container and can't touch the host OS.
  • Firewall Rules: I explicitly closed all ports I don't use (especially Port 25) in my server configuration.
  • WAF: I enabled Cloudflare protection to help filter out these kinds of malicious requests before they even hit my IP.

It was a painful way to learn about server security, but definitely a lesson I won't forget.