writeup ·

48 Hours on a SCADA Honeypot

A SCADA-themed honeypot on Hetzner caught WannaCry samples still propagating in 2026, Outlaw/mdrfckr botnet credential stuffing, Solana validator credential harvesting, and automated Modbus/TCP scanning.

What happens when you disguise a Hetzner VPS as a small-town water treatment plant and point it at the internet? 47,771 events, eight attack campaigns, and a nine-year-old piece of North Korean malware. In two days.

The setup

On a Friday night in April 2026, I deployed a high-interaction honeypot on a Hetzner Cloud server in Ashburn, Virginia, themed as the internet-facing SCADA gateway for a fictional municipal water utility called Cedar Creek Municipal Water Authority.

The goal wasn’t groundbreaking research. I just wanted to watch attackers do their thing.

The infrastructure was built on T-Pot, Deutsche Telekom’s open-source honeypot platform, running 39 Docker containers across about 20 different honeypot services. The box emulated SSH, Telnet, HTTP/HTTPS, SMB, SMTP, SIP, Modbus, S7comm, SNMP, IEC 60870-5-104, BACnet, IPMI, and a handful of others, all on a single IP address.

The ICS theming was the fun part. Conpot was configured to respond to SNMP queries with Siemens, SIMATIC, S7-300, a common PLC found in water treatment plants. Cowrie’s fake SSH filesystem was populated with directories like /opt/schneider/ and /var/log/scada/, a .bash_history full of modpoll commands, and an MOTD referencing NIST 800-82.

On top of T-Pot, I built a custom employee portal on port 9443: a Cedar Creek MWA login page styled to look like a dated ASP.NET intranet running on IIS 8.5. It served a robots.txt that “accidentally” exposed internal paths like /admin/, /scada/, /vpn/, and /owa/. The unprotected directories contained an Active Directory LDIF export with employee usernames, and a Swagger API doc with a dev key in the comments. The protected directories (behind basic auth that accepted the same AD credentials — password reuse as the vulnerability) contained IT meeting minutes, PLC commissioning notes, a disaster recovery plan, and a VPN config file. Credentials were buried inside boring operational documents, not sitting in files labeled passwords.txt. The whole thing was designed as a realistic attack chain: find the portal, discover usernames from the AD export, find passwords through credential reuse, log in, explore the fake intranet.

Nobody found the portal credential chain in 48 hours. The bots had other priorities.

The first hour

Within minutes of going live, the traffic started. By the end of the first hour, the dashboard showed 33 attacks. Cowrie was catching SSH banner grabs: 0.2-second connections from scanners probing port 22 and immediately disconnecting. The SSH client string was SSH-2.0-libssh-0.2, a lightweight library commonly used by automated scanners. No humans yet, just bots mapping what was listening on the Hetzner IP range.

Port 445 (SMB) was the most-hit port in the first hour. That turned out to be a preview of something much more interesting.

By the time I went to sleep at 3 AM, roughly two hours after deployment, the event count was at 8,500. The username and password tag clouds in Kibana told a story: alongside the expected root/admin and root/123456 attempts, there were clusters of Solana-specific credentials. solana/solana, sol/sol, solnode/solnode, helius/helius, firedancer/firedancer, raydium/raydium. Someone was running a credential list specifically targeting Solana validator nodes on Hetzner infrastructure.

Campaign 1: mdrfckr

The first full intrusion sessions appeared within a few hours. Every single one followed an identical playbook: strip immutable flags from .ssh with chattr -ia, nuke and recreate the .ssh directory, plant a single authorized key with the comment mdrfckr, check CPU info and core count, change the root password with chpasswd, kill competing malware processes, blank out hosts.deny, then run a full hardware survey (CPU model, RAM, disk, architecture via uname, lscpu, free, and df). Every session, same order, same commands.

The SSH key comment says it all: mdrfckr. This is the Outlaw botnet (also known as Dita), a well-documented cryptomining operation linked to Romanian cybercriminals. The same SSH public key was first observed on VirusTotal on July 5, 2018. Eight years later, it’s still being planted on compromised servers.

I captured 155 intrusion sessions from this botnet across the 48-hour window, originating from 10+ unique IPs across the United States, France, Hungary, China, and the Seychelles. Every session used the same SSH key, the same command sequence, and a unique randomly-generated password for each compromised host.

The playbook breaks down like this:

  1. Remove immutable flags from .ssh (chattr -ia). Undoes any file protection a previous administrator set.
  2. Nuke and replace SSH keys. Deletes all existing authorized keys and plants their own, locking out the legitimate owner and any other attacker who previously planted keys.
  3. Change the root password to a random string. Each session uses a different password (K3LZwFN3e1vq, PLpL9jEf2t3p, DoEizj7TZjn7, etc.). This prevents other botnets using the same leaked credential list from getting back in.
  4. Kill competing malware. pkill -9 secure.sh; pkill -9 auth.sh terminates common persistence scripts used by rival botnets.
  5. Blank hosts.deny. Removes IP-based access controls.
  6. Full hardware recon. CPU model, core count, RAM, disk space, architecture. This determines whether the box is worth deploying a miner on and which binary to use.

The competing-malware-kill step is worth noting. The threat model for this botnet isn’t the system administrator discovering the compromise. It’s another botnet stealing the compute. Every compromised server is contested territory, and the first attacker to change the locks wins. The playbook dedicates more steps to locking out other botnets than to evading detection.

A previous researcher documented this botnet in October 2022, finding 12,913 unique IPs across 152 countries over a two-month period. In 2022, the botnet used SSH-2.0-libssh-0.6.3. My honeypot observed SSH-2.0-libssh_0.11.1. They’ve upgraded the SSH library while keeping the exact same SSH key and command sequence for eight years.

Campaign 2: Solana validator hunters

The second-most-active campaign was specifically targeting Solana blockchain validator nodes. Hetzner has historically hosted a large fraction of Solana’s validator infrastructure, to the point where Solana’s geographic concentration on Hetzner was considered a decentralization risk for the network.

The credential pairs tell the story: solana/solana (141 attempts), sol/sol (129), solv/solv (115), sol/123 (95), solv/123456 (95), sol/solana (70), solana/sol (64), solv/12345678 (63), node/node (45), solana/solana123 (44).

The usernames aren’t random. solana, sol, solv, and node are common naming conventions for Solana validator operators. helius (a Solana RPC provider) and firedancer (Jump Crypto’s validator client) also appeared in the credential lists.

Every successful Solana-targeting session ran exactly one command: /bin/./uname -s -v -n -r -m. The unusual /bin/./ path prefix (using a redundant ./) is likely an evasion technique against simple command logging that watches for /bin/uname or just uname. All sessions came from IP ranges in Bulgaria (195.178.110.x, Techoff Srv Limited), the Netherlands (2.57.122.x), and a separate cluster (92.118.39.x).

The objective here is straightforward: Solana validators hold keypair files on disk that control staked tokens. A typical validator might have hundreds of thousands of dollars in staked SOL. SSH access to a validator node means access to the keypairs, which means draining the stake.

Campaign 3: WannaCry, still going

This was the surprise.

Dionaea, the malware-capturing honeypot on port 445 (SMB), collected 16 binary samples over 48 hours. Every single one was 5,267,459 bytes, a PE32 DLL compiled for x86 Windows. The compile timestamp in the PE header: May 11, 2017.

Strings analysis turned up the kill switch URL:

http://www.iuqerfsodp9ifjaposdfjhgosurijfaewrwergwff.com

WannaCry. Nine years old and still propagating.

For anyone unfamiliar: this is the ransomware attributed to North Korea’s Lazarus Group that tore through the world in May 2017, spreading via EternalBlue (MS17-010), an SMB exploit developed by the NSA and leaked by the Shadow Brokers. It’s a worm, so it self-propagates without human interaction. Marcus Hutchins famously halted the ransomware payload by registering the kill switch domain above on May 12, 2017. The domain is still registered, but now it redirects through affiliate links to… Kohl’s department store. Every zombie machine checking the kill switch is generating impressions for someone’s retail affiliate account.

The infected machines hitting my honeypot are almost certainly IoT devices, embedded systems, or neglected servers that were compromised in 2017 and have been scanning the internet continuously ever since. Nobody has cleaned them. They just keep going.

Campaign 4: PHPUnit remote code execution

Tanner, the web application honeypot, logged 789 requests probing 589 unique URL paths. The dominant pattern was systematic scanning for eval-stdin.php, a known vulnerability in PHPUnit (CVE-2017-9841) that allows remote code execution. The scanner tried every common web application directory as a prefix to the same path (/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php): laravel/, yii/, zend/, cms/, api/, demo/, crm/, admin/, blog/, public/, tests/, and dozens more. Automated webshell planting. Find the vulnerable endpoint, upload a PHP shell, establish persistence.

Campaign 5: Android Debug Bridge

ADBHoney captured an attempt to compromise the server via Android Debug Bridge (port 5555). The command created a hidden directory in /data/local/tmp and tried four different download methods in sequence (wget, busybox wget, curl, toybox wget) to pull an ARM7 binary called parm7 from a remote server at 196.251.107.133. The fallback chain exists because IoT devices running Android have inconsistent tooling installed. The binary name indicates it’s compiled for ARMv7, the most common architecture in Android phones, tablets, IP cameras, and smart TVs. IoT botnet recruitment.

What didn’t happen

No one discovered the employee portal’s credential chain. The Cedar Creek MWA login page was visited by scanners and indexed by Censys, but no attacker attempted to log in with credentials harvested from the AD export in /owa/. The multi-stage attack chain went completely untested. This isn’t surprising for a 48-hour window dominated by automated botnets. Targeted human attackers operating on ICS-specific intelligence would need more time to discover and investigate the portal.

No attacker interacted with the SCADA theming in Cowrie’s SSH either. The fake filesystem with Schneider Electric directories, Modbus register maps, and SCADA event logs went unexplored. Every successful Cowrie session ran the same automated recon scripts. None of the attackers typed ls and looked around at what kind of system they’d compromised. The mdrfckr botnet treats every compromised server identically regardless of what’s on it.

So what

The main thing I took away from this is how deeply impersonal internet-facing attacks are. 47,771 events and not one of them was about Cedar Creek Municipal Water Authority. Nobody cared that this was themed as a water plant. Nobody explored the SCADA directories. The bots don’t read. They sweep the Hetzner IP range (and the rest of the IPv4 space) with automated tools, and my server happened to be on a routable address.

The other thing that stuck with me is how old everything is. WannaCry is from 2017 and still propagating. The mdrfckr SSH key has been active since 2018. CVE-2017-9841 is being scanned for in 2026. The internet is full of stuff that just keeps running because nobody has bothered to turn it off, attacking other stuff that just keeps running because nobody has bothered to patch it. It’s less of a war zone and more of an abandoned parking lot where someone left a bunch of Roombas on.

The whole project cost about $3.50 in Hetzner compute. Fun times.


← all research