I rented three servers this afternoon, one in Frankfurt, one in New York, one in Singapore. I gave the addresses to nobody. They are not in DNS, not in a certificate, not committed anywhere, not pasted into a chat. The only systems that knew these machines existed were the ones I rented them from.
Frankfurt got its first uninvited connection 107 seconds after it booted. Singapore took 48. Over the next three quarters of an hour the three of them logged 3,480 connection attempts from 860 different addresses, and 1,085 attempts to log in with a real password.
None of this is aimed at me. Nobody chose these machines. This is just the background radiation of a public IP, and I wanted to measure it rather than wave at it.
The setup
Each box ran a small Python program that listens on the ports things actually knock on: telnet on 23, HTTP on 80 and 8080, TLS on 443. The SSH honeypot sat on 2222 so the machine's real SSH could keep port 22 for me to administer it.
Every listener is a fake. The telnet service shows a Ubuntu login prompt and rejects every password. The HTTP one logs the request and returns 404. Nothing is ever let in, and none of the credentials collected are used for anything. Alongside the listeners, tcpdump recorded every inbound TCP SYN that was not a reply to my own traffic, so I could count knocks on ports where nothing was listening as well.
The whole thing ran for about 45 minutes per region and then destroyed itself. Total cost was under three cents.
First contact is fast
| Region | First unsolicited packet |
|---|---|
| Singapore | 48 seconds after boot |
| Frankfurt | 107 seconds |
| New York | 324 seconds |
There is no grace period. The address space is scanned continuously by everyone at once, so the clock starts the moment the IP is routable, not when you deploy anything. Whatever you were planning to harden after the demo works is already being probed.
The volume is steadier than the totals suggest
The raw totals are lopsided. Frankfurt logged 2,325 attempts against Singapore's 516, which looks like a big regional difference until you look at who sent them. A single host on Korea Telecom's network sent 1,663 packets to Frankfurt on its own, almost all to telnet. It was hammering one box, not scanning.
Take that one address out and the three regions agree closely:
| Region | Attempts/hour, busiest source removed |
|---|---|
| Frankfurt | 829 |
| New York | 759 |
| Singapore | 677 |
That is the real number I would quote: a fresh public IP, anywhere, fields somewhere around 700 to 800 unsolicited connection attempts an hour from the general noise, plus the occasional individual who decides to lean on you.
The shape of the crowd backs this up. Of the 860 distinct addresses, 672 of them, 78%, sent exactly one packet in the whole window. This is overwhelmingly wide, shallow scanning: touch an address, note what answers, move on. Only a handful of hosts ever settled in to actually try things.
What they want
Ranked by connection attempts across all three boxes, the ports asked for most:
- 23, telnet. By a mile, once the Korea Telecom host is included. This is the IoT story. Telnet is where routers, cameras and DVRs live, and the botnets never left.
- 8088. Steady on every box, from many sources. That is the Hadoop YARN and various admin panels port. Someone maintains a list.
- 22, SSH. Constant but not dramatic.
- 587 and 465. On New York specifically, one host tried the mail submission ports 50 times each. Freshly rented cloud IPs are prized for sending spam before anyone has reported them.
- 5555. Android Debug Bridge. A phone or TV box with ADB exposed is a free computer.
The HTTP requests were almost all bare probes to / with no Host header, which is a scanner checking whether anything answers rather than a browser. Only a trickle carried a real hostname, and none of them matched anything a previous tenant of these IPs might have run.
The passwords are the interesting part
The telnet listener collected 1,085 login attempts with an actual password, covering 107 distinct username and password pairs. This very nearly did not work, and the reason it failed at first is the best thing I learned all day. A bot connects, sends the username, and then waits for the server to negotiate telnet options before it sends the password. My first prompt did not speak that negotiation, so I collected a pile of usernames and no passwords at all. The fix was to answer the option negotiation the way a real telnet daemon does. Then the passwords poured in.
Here are the most common, by frequency:
| Password | Times tried |
|---|---|
| annie2015 | 43 |
| OxhlwSG8 | 42 |
| S2fGqNFs | 40 |
| annie2014 | 40 |
| annie2016 | 39 |
| motorola | 39 |
| tlJwpbo6 | 39 |
| 7ujMko0admin | 37 |
| epicrouter | 34 |
| 123456 | 33 |
If you have ever looked at a Mirai source dump these will be familiar. 7ujMko0admin is a Dahua camera backdoor. epicrouter is a Conexant DSL default. Zte521, further down the list, is a ZTE router default. The annie20xx run and the random eight-character strings like OxhlwSG8 are a specific botnet family cycling its built-in list. 123456 is in there too, because it always is.
The thing to notice is what is absent. Almost nobody tried to guess a human's password. There were no dictionary runs against admin with password1, qwerty, letmein. The traffic is not trying to break into accounts. It is trying every known factory default for every mass-produced device, at machine speed, against every address at once. You are not being attacked as a person. You are being enumerated as a device that might have shipped with a known password.
What I got wrong on the way
Three things, and the first two cost me real money, though not much of it.
I tried to move the machine's real SSH off port 22 so the honeypot could have it, by editing sshd_config. On Ubuntu 24.04 that does nothing, because SSH is socket-activated by systemd and the config's Port line is ignored. My attempt to fix that through the systemd socket instead took the box off the network completely, and I had to destroy it and start over. I did it a second time on another box before I accepted that relocating the admin SSH was not worth the risk and left it on 22, with the honeypot's SSH on 2222. The teardown ran in a finally block throughout, so both dead boxes were destroyed within seconds rather than billing overnight.
I also nearly reported a beautiful, clean finding of zero. My TLS parser pulled the server name out of every handshake and returned nothing, every time, and I almost wrote "nobody sends SNI to a bare IP". The parser was broken: it ended in a .decode('idna') that throws on an ordinary hostname, and a broad except turned every exception into a tidy None. A result of zero that comes from a bug looks exactly like a result of zero that is true. The only defence is to test your instrument against a positive control, so I pointed a real client with a known hostname at it and watched the None come back when it should not have.
Run it yourself
The listeners are plain sockets. The telnet negotiation is the one non-obvious part, and it is the difference between collecting passwords and collecting nothing:
IAC = 0xff
def handle(conn):
# DO suppress-go-ahead, WILL echo, WILL suppress-go-ahead
conn.sendall(bytes([IAC, 0xfd, 3, IAC, 0xfb, 1, IAC, 0xfb, 3]))
conn.sendall(b'\r\nUbuntu 24.04.1 LTS\r\nlogin: ')
user = read_line(conn) # strip IAC sequences from the reply
conn.sendall(b'Password: ')
pw = read_line(conn)
log(user=user, password=pw) # then always reject
conn.sendall(b'\r\nLogin incorrect\r\n')
The connection count came from tcpdump rather than the listeners, because I wanted to see knocks on ports where nothing answered too. This is where I burned an hour the first time I did this sort of thing, so it is worth saying plainly. If you capture everything you will mostly record your own machine talking back to apt and pip, and the number comes out enormous and meaningless. You want inbound SYN packets only, the opening knock of a connection and nothing else, and you want to exclude your own address so the SSH session you are watching from does not count itself. That is this:
tcpdump -i eth0 -nn -q \
"tcp[tcpflags] & tcp-syn != 0 and tcp[tcpflags] & tcp-ack == 0 and not host YOUR_IP" \
-w knocks.pcap
Mine were the cheapest droplets DigitalOcean rents, one in Frankfurt, one in New York, one in Singapore, each gone the second its capture ended. I did not use three regions to compare them against each other. I used three because I wanted to know if this was some quirk of one datacentre or just what the internet is like everywhere, and it is everywhere: the knocking started within a couple of minutes on all three, wherever they were.
What to do about it
What stayed with me is how dumb all of it is. Nothing tried to be clever. It was 860 machines reading down the same list of factory passwords for cameras and routers, firing it at every address they could reach, and my three boxes were simply in the range that afternoon. If your server has no default credentials and nothing extra listening, this traffic does not get defeated, it just misses, because it was never looking for you in the first place.
So the advice everyone already gives turns out to be right, which is not surprising, except now I have watched why. Keep telnet off the internet. Change the password that shipped with the box, especially on the cameras and routers where it never gets changed because nobody signs back into a camera once it is on the wall. And the idea that you deploy first and harden afterwards does not survive contact with the numbers here, because Singapore was being probed 48 seconds in. The afterwards you were counting on is already gone.
Top comments (6)
First contact at 48 seconds is the detail that should reframe everyone's boot sequence. The scanners find the IP before your provisioning script finishes running - which means there's a window where your fresh server is reachable with whatever the base image ships, and that window is measured in seconds, not hours. Default-deny has to be a property of the image, not a step in the setup script; "harden it after boot" is a race you already lost. The other reading of your numbers: three throwaway VPSes are a free sensor network. 3,480 attempts in 45 minutes is the background radiation of the internet, and anyone running infrastructure can measure it for the cost of a coffee. The IoT factory defaults being a third of the login attempts is its own quiet horror - those credentials are a decade old and still the highest-yield thing to try.
The image point is right and the numbers make it worse than you put it. Singapore's 48 seconds is shorter than cloud-init finished on the same box, so it is not a race you lose narrowly, it is a race that was over before your script was scheduled.
One catch on the sensor network reading. What three fresh unannounced addresses measure is specifically the traffic that finds you with no prior knowledge, which is the untargeted floor. It is a genuinely cheap baseline and it is not a threat model, because everything aimed at you rather than at the address space is invisible to this setup by construction.
On the credentials: 1,085 of 3,480 being factory defaults is not inertia, it is a correct bet. The devices that shipped with those passwords do not get firmware updates, so the hit rate never decays. The list is a decade old because a decade of it still answers.
I'm curious, what if you ran it IPv6 only? No v4 exposure at all?
Did not test it, so I have no numbers for you. All three were v4.
What I would expect, and why: a single /64 is 2^64 addresses, so the exhaustive sweep that produced my 3,480 attempts is not a thing anyone can run. The untargeted layer just disappears. What replaces it is discovery rather than scanning, so DNS and rDNS, certificate transparency, address leakage in the box's own outbound traffic, and public hitlists. That means the 48-second first contact goes away, but "nobody knows this address" holds only for as long as the machine never announces itself, and a v6 box that makes one outbound request to something logging has announced itself.
The provider still knows, and allocation ranges are published. Good follow-up though, and cheap to run.
My thought was that if this is brute force sweeps (not impossible, given that it's limited address space, actually easy to iterate through for v4), then v6 would mitigate it, it'd be 'sheer dumb luck' if someone guesses it. If the box announces it, but only on the server provider's own internal network, then it's even more concerning, because that'd mean that they arent securing their internal traffic. Which is a pretty big deal if that's the case...
Some comments may only be visible to logged-in visitors. Sign in to view all comments.