I didn’t plan on spending my Tuesday morning scrubbing server logs, but here we are.
If you manage any Next.js deployments—especially anything public-facing that hasn’t been touched since the holidays—you need to check your infrastructure. Right now.
We’re looking at a massive automated campaign that just swept through the internet like a brushfire. They’re calling it PCPcat. In roughly 48 hours, this thing compromised over 59,000 servers. Well, that’s not entirely accurate—I saw the alerts start firing on our staging cluster around 3 AM, and by the time I had my coffee, the community was already on fire.
The success rate on this campaign is terrifying. We’re talking about a 64% hit rate on scanned targets. That’s not a “spray and pray” script kiddie operation; that’s a precision strike exploiting specific, unpatched RCE flaws.
The Mechanics of the Mess
So, what actually broke?
The attack vectors are tracking as CVE-2025-29927 and CVE-2025-66478. If you remember the scramble we had late last year with image optimization exploits, this feels eerily similar, but the payload delivery is different.
The attackers are using a scanner script dubbed react.py. The irony of naming a malware distributor after the library it exploits isn’t lost on me, but I’m too tired to laugh. This script probes public deployments looking for specific remote code execution (RCE) vulnerabilities in the Next.js image optimization API and certain middleware configurations.
Once it gets a foothold, it doesn’t just deface the site. It scrapes credentials. Environment variables, AWS keys, database connection strings—anything sitting in your runtime environment is fair game.
I pulled a sample of the attack traffic from one of our honeypots running on a t3.micro instance. The User-Agent string was spoofed to look like a standard Googlebot, but the payload in the query parameters was unmistakable.
Checking Your Logs (Do This Now)
Don’t assume you’re safe just because you’re behind Cloudflare. I saw requests bypassing standard WAF rules because they were encoding the payload in a way that looked like legitimate base64 image data.
Here is a quick Python snippet I whipped up to scan Nginx or Vercel access logs for the specific signature associated with PCPcat. It looks for the encoded shell command patterns that react.py tends to inject.
import re
import sys
# Usage: python scan_logs.py access.log
# specific signature observed in Jan 2026 attacks
def scan_for_pcpcat(logfile):
# The payload often contains double-encoded shell operators
suspicious_pattern = re.compile(r'(?:%26%26|%7C%7C|;|%3B).*(?:wget|curl|bash|sh)', re.IGNORECASE)
print(f"Scanning {logfile} for PCPcat signatures...")
with open(logfile, 'r') as f:
for line in f:
if suspicious_pattern.search(line):
# Check if it targeted the image endpoint or API routes
if '/_next/image' in line or '/api/' in line:
print(f"[ALERT] Potential compromise attempt found:\n{line.strip()}\n")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Point me at a log file, please.")
else:
scan_for_pcpcat(sys.argv[1])
I ran this against a client’s logs from Monday. We found 14 attempts. And you know what? Two got a 200 OK response. That was a bad phone call to make.
The “Gotcha” No One Mentions
Here is the thing that really ticked me off while debugging this.
Most people are just patching their next version and calling it a day. That is not enough. If you were hit, the attackers likely already exfiltrated your .env contents. I found evidence on a compromised server where the malware simply cat’d the environment variables and posted them to an external IP. Patching the hole closes the door, but it doesn’t change the keys they stole.
I had to rotate SendGrid keys, AWS secrets, and database passwords for three different projects yesterday. If you see a successful 200 response on a suspicious request in your logs, you have to assume your secrets are burned. Rotate them. All of them.
Why react.py is So Effective
I took a look at the decompiled logic of the scanner (someone dropped a sample in a private Discord I’m in). And you know what? It’s surprisingly competent code.
It doesn’t just blast every IP. It checks for the X-Powered-By: Next.js header (which, seriously, you should have disabled in your next.config.js ages ago) and fingerprints the version. If it detects a version between 14.1.0 and the late-2025 patches, it launches the specific exploit for that version.
This targeted approach is why they hit a 64% success rate. They aren’t wasting time on patched servers or non-Next.js stacks. They know exactly what they are looking for.
How to Fix It
First, update. Obviously.
If you are on the v14 branch, you need to be on the latest patch release released this week. If you moved to v15 late last year, ensure you are on 15.1.4 or higher (checked as of this morning).
Second, disable that header. It’s just painting a target on your back.
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
poweredByHeader: false, // Turn this off. Seriously.
// ... other config
}
module.exports = nextConfig
Third, if you are self-hosting Next.js (using Node.js directly or Docker), make sure your user permissions are locked down. The PCPcat malware tries to write a persistence script to /tmp or /var/tmp. If your Node process runs as root (please tell me it doesn’t), they own the box.
I saw one instance where the attack failed solely because the application user didn’t have write permissions to the directory the script tried to drop the payload into. Basic Linux hygiene saved that server.
My Take
It’s frustrating. We push Next.js because it makes the frontend workflow smooth, but the complexity of the server-side features—especially the image optimization layer—has become a massive attack surface.
And you know what? This incident reminds me of the PHP exploits from the mid-2010s. We’re just reinventing the same security nightmares in JavaScript now.
Check your logs. Rotate your keys. And maybe, just for peace of mind, put a stricter WAF rule in front of /_next/image if you aren’t actually using it.











