securityinfrastructureincident-responseself-hostingaws

The Uninvited Tenant

by James H. Gordy

The Uninvited Tenant

"How did you go bankrupt? Two ways. Gradually, then suddenly." Hemingway understood that calamity is either under-observed or noticed when it's already too late. Servers are the same. The load average had been creeping up for an hour, higher than usual but not in the red, the kind of number that's easy to misread as good news. Maybe we've got traffic. Break for lunch, no second thought. By the time I sat down with the mug of afternoon brown, htop was a horror show. CPU pinned at 100%. Load average at 14 on a 2-core box. Something was eating it, and it wasn't me.

The Old Man and the C

htop told me what I already knew, then told me something I didn't. There was a process called fghgf chewing through a CPU core. fghgf. Deliberately named to make any developer think they'd shipped a careless local test, and that's what I would've thought if not for the config file parked next door in /tmp invoking a small grep loop scanning for rival miners, which is how I learned that the freeloader squatting in my tmp directory had competitors and was worried about them. This wasn't a symptom. This was an infection.

I killed it. Twelve seconds later something else came up on a different port with a different keyboard-mash for a name. Then another. I'd just entered a game of Hack-A-Mole.

Illustration of a flustered sysadmin playing a Hack-A-Mole arcade cabinet, every mole wearing a ski mask representing rogue cryptomining processes

The hunt

You stop swatting and start tracing. Where did it get in. What is it touching. What's it talking to and on what schedule. Cryptominers are not stupid. They have cron jobs, systemd units, and hidden directories in places that look enough like real system paths to slide past a tired eye. Persistence is the whole point: the miner you can see is the decoy for the three you can't.

The entry point turned out to be a self-hosted Next.js app running an outdated version, vulnerable to CVE-2025-29927, the middleware authorization bypass that Vercel disclosed in March 2025. CVSS 9.1. Critical. The flaw is, frankly, the kind of thing you'd be embarrassed to ship: Next.js used a header called x-middleware-subrequest internally to prevent infinite middleware loops, and then trusted that header without verifying its origin. Set it on an inbound request from the open internet and the framework cheerfully waves you through, past every auth check the developer wrote.

If you want the gory walkthroughs, Snyk and Datadog Security Labs both did a proper job. The short version: anything before 14.2.25 on the 14.x line, before 15.2.3 on 15.x, and a long tail of older releases. Vercel-hosted apps were patched at the platform level. Self-hosted ones were on their own. I had tried to have an "independent day", but invaders had decided everything needed to be blown up.

Server, sanitised

There's a particular satisfaction to a ruthless clinical nuke: taking back control through experience and permissions, both of which the intruder lacks and you don't. You wrote the access policy. You hold the keys. These assholes got in, but they aren't getting out.

Stop the infected app. Pull the disk for forensics if you're being a grown-up, otherwise back it up off-box. Scrub the persistence: cron tabs, systemd units, anything weird in /etc/rc.local, hidden dot-files in /root dressed up to look like real services. Kill the orphans. Bind anything internal-only back to 127.0.0.1 where it should have been from the start. Reboot. Then sit and watch htop and ss -tlnp for a stretch, ready to shoot from the hip.

Patched the framework. Redeployed clean from git. Wrote a detection script while it was all still fresh, because the next time someone tries this on one of my other boxes I'd rather find them in 30 seconds than three hours. The script lives in a private repo and gets a healthy run on every droplet I touch.

Illustration of an engineer working alone late at night, laptop screen glowing in a dark room, hunting a malicious process while takeaway grows cold on the desk

Sometimes a paper cut. Sometimes Pompeii.

The cryptominer was a paper cut. Annoying, expensive in time and dignity, but bounded. One box, one bad afternoon, no permanent damage. The thing about giving an attacker access to your infrastructure is that the floor is a paper cut. There is no ceiling.

A few years ago, a client's AWS account I was responsible for got popped. Not my account, not my money on the line, but my problem. The first sign was the smallest possible one: I went to log in one morning and the credentials didn't work. No usage spike yet, no scary numbers, just a door that used to open and now didn't. I opened a support ticket flagging that I'd been locked out and that I suspected a compromise.

AWS support shrugged. Probably a temporary glitch. Have you tried clearing your cookies. The tone was the polite, faintly bored one you reserve for someone overreacting to a minor inconvenience. I escalated. Same answer.

The key under the plant pot

What AWS didn't know, and what I'd half-forgotten myself, was that I had a backdoor into one of the running instances. Not a sinister one. A pragmatic one. The kind of out-of-band access an honest sysadmin leaves on a client's infrastructure at 2am on a Friday, six months earlier, on the off chance future-them is locked out of a console for a reason they can't yet imagine. Not a vulnerability. A lifeboat. The client didn't know about it. They wouldn't have understood why I wanted it. I left it anyway, because someone has to think about the morning when the door doesn't open, and on that engagement that someone was me.

Illustration of a brass key half-hidden under a terracotta plant pot on a stone doorstep, representing emergency out-of-band server access left by a cautious engineer

I used it. Got onto the machine, found exactly what you'd expect: a cryptominer, the same shape of opportunist as the fghgf story above, just running on infrastructure with a much higher ceiling. Killed the process. Killed the instance. Pulled the credentials it had been using. The attackers were burning compute for Monero on AWS's dime, which would shortly be my client's dime, and I was now actively setting fire to their setup from inside while AWS support was still asking me whether I'd tried a different browser. None of this was in the contract. None of it would have been billable. It was a Saturday.

Three days later the bill arrived anyway, because the time between the lockout and me getting back in had been sufficient for the attackers to spin up enough hardware to render a Pixar film. Final bill landed around $189,000.

Then the structural insult. AWS still wouldn't give us proper access to the account "for security reasons", which is a brave principle to invoke after the security has, demonstrably, already left the building. They would however cheerfully send invoices. The position was, broadly, that we owed them six figures for a compute spree we'd reported as suspicious before it started and partly disrupted ourselves while their support team suggested cache-clearing.

What saved us was screenshots. Every ignored support reply from those first three days, timestamped and archived, the lockout flag clearly preceding the first malicious instance launch. A paper trail showing we'd flagged it as a compromise on day zero, they'd told us to relax, and the meter had run anyway. That trail eventually got the bill written off. Without it: ruinous. With it: a story you tell at parties for the rest of your life.

Two morals, and they're not the same one. The thieves wanted compute. Bounded goal, bounded damage, and once I got onto the box I could end them. The cloud provider's incentive runs the other way. Every additional minute of uncertainty is money on the meter, and the meter is theirs. The thieves break in. The platform charges you rent on the broken window and bills you for the draft. That's the part nobody puts in the brochure, and it's the part you have to architect around.

The other moral is quieter, and it's about the job. Past-me left a backdoor on a client's box because past-me thought ahead. Past-me took screenshots in real time because past-me was, frankly, terrified. Both of those happened because the account on the line wasn't mine. Working on someone else's infrastructure is a particular kind of responsibility. You're not just protecting servers. You're protecting the company that trusted you with them, and the people whose jobs are downstream of those servers staying up. Diligence is when past-you is kind to future-you. When the work is for a client, it's also kind to them. That's most of what the job is.

Receipts and rituals

There is a quiet skill, mostly invisible, that separates the engineers who survive their careers from the ones who get burned by them. It is not technical brilliance. It is not even particularly impressive. It is the muscle memory of leaving a paper trail. Screenshots before you click anything dangerous. Logs piped somewhere off-box. Process trees captured before you start killing things. A backup of the support ticket you're about to escalate. The boring stuff that feels paranoid until it's load-bearing.

You don't earn the trail in the moment. You earn it months earlier, in the small decisions nobody saw. The detection script you wrote on a Sunday because the previous Saturday was bad enough to want to never repeat it. The emergency-access path you set up on a client's infrastructure because at 3am six months earlier you thought what if. The version pin you held the line on when a junior dev wanted to bump it without reading the changelog.

Philip Larkin wrote about parents passing on their faults. He had a bleak view of the inheritance. Past-you and future-you have a kinder option. You can leave the right things behind on purpose. A note in the runbook. A comment in the config. A timestamp on a ticket. A patch you applied when it still felt optional.

The freeloader in /tmp is gone. The bill for the Pixar farm got written off. The detection script is still there, watching. So is the lifeboat on the client's box, which they still don't know about. That's the deal. You leave the next person breadcrumbs, even if the next person is yourself, even if nobody ever notices, because one day they'll need them and the alternative is a very long Saturday.

Illustration of a cloud computing invoice dramatically on fire on a desk while an engineer calmly photographs evidence on his phone in the background

Frequently asked questions

How do I know if my server has been compromised by a cryptominer?

Look for unexplained CPU spikes, unfamiliar processes in htop or top (often with random names like 'fghgf'), new cron jobs or systemd units you didn't create, and suspicious files in /tmp. Deviant Ops offers infrastructure auditing and can build custom detection scripts tailored to your stack.

Is self-hosted Next.js safe, or should I use Vercel?

Self-hosted Next.js is safe if you keep it patched and follow hardening best practices. Vercel patches platform-level vulnerabilities like CVE-2025-29927 automatically, but self-hosted gives you full control. Deviant Ops specialises in secure self-hosted deployments with automated patching, monitoring, and intrusion detection.

What should I do if my cloud account gets compromised?

Document everything immediately - screenshots of support tickets, timestamps of suspicious activity, and billing anomalies. Revoke compromised credentials, kill malicious instances, and escalate with your provider using your paper trail. Deviant Ops provides incident response services and can help architect infrastructure to limit blast radius.

How can I protect my infrastructure from cryptocurrency mining attacks?

Keep frameworks and dependencies patched, restrict outbound network access, monitor CPU and process lists, use intrusion detection tools, and maintain off-box logging. Deviant Ops builds hardened infrastructure with proactive monitoring, automated alerting, and detection scripts that catch miners within seconds.

← back to blog