DEV Community

Remdore
Remdore

Posted on

A new server gets its first unsolicited packet in 3.77 seconds

I rented a $6 server, gave it a fresh IPv4 address nobody had used before, and started a stopwatch.

The first unsolicited packet arrived 3.77 seconds after the listener came up. It was a TLS ClientHello aimed at port 8443, from a host in a Linode range, and it landed before the machine had finished its second minute of existence.

Seventy-five minutes later I had 1,212 events from 84 addresses across 15 ports. The interesting part turned out not to be the speed. It was that counting the same traffic three different ways produced three different answers, and two of them were wrong.

The setup

One droplet in Frankfurt, Ubuntu 24.04, the cheapest size that runs a Python process. Cloud-init moves the real sshd to port 62222 so the collector can own port 22, then starts a single asyncio program listening on 26 ports: the classic ones (21, 22, 23, 25, 445, 3306, 3389, 5432, 6379, 27017) and a spread of HTTP-ish ones (80, 443, 3000, 5000, 8080, 8443, 9200).

The collector never authenticates anyone and never executes anything. It sends a protocol-appropriate banner, reads whatever the client says, writes a JSON line, and hangs up. A fake MySQL greeting, a telnet login: prompt, an nginx 401. Nothing on that box could be pivoted through, because there is nothing behind the banners.

Port 62222 is excluded from every number below. That is my own SSH and counting it would be measuring myself.

The timeline

Droplet created at 18:18:36 UTC. Collector live 99 seconds later. Then:

after collector start first contact on
3.77s 8443
37.95s 443
4m 36s 80
6m 53s 22
7m 57s 445
8m 27s 23
12m 11s 5000
35m 52s 27017
39m 23s 25
43m 31s 5432
60m 59s 1433

The first HTTP request, at 4 minutes 36 seconds, was for /.env.

Not the root path. Not a probe for a login page. A request for the file where people keep their database passwords and API keys, from a host that had known this address existed for less than five minutes.

The first password guess came at 8 minutes 27 seconds, on telnet, and the username was admin. Across the window, root was tried five times and admin four.

Three ways to count, three different winners

Here is where it gets interesting. I ranked the ports by how much traffic they got. Then I ranked them again by how many distinct addresses sent that traffic. Then a third time by how many distinct networks those addresses belonged to.

port events distinct IPs distinct networks
445 SMB 890 2 2
443 HTTPS 71 22 13
27017 MongoDB 58 1 1
5000 HTTP 55 5 2
5432 Postgres 43 31 1
80 HTTP 42 7 7
23 telnet 23 3 3
22 SSH 10 6 6

By event count, SMB wins by a mile: 890 of 1,212 events, 73% of everything. That number is worthless. It came from two hosts, one of which simply never stopped. Any chart built on event counts is a chart of who has the loudest loop.

So count distinct sources instead, which is the obvious fix. Now Postgres wins with 31 addresses, four times more than anything else. A reasonable conclusion: port 5432 attracts the widest spread of attackers.

That is also wrong. All 31 addresses belong to a single UK company, Driftnet Ltd, and they touched exactly one port. Forty-three connections, 31 addresses, port 5432 and nothing else. That is one operator with an address pool, not 31 independent parties.

Count distinct networks and the ranking finally behaves: HTTPS with 13, HTTP with 7, SSH with 6. The boring answer — that web ports attract the most separate actors — is the one that survives.

Two of the three metrics told me something confident and false. The unit you pick decides the finding.

What they were actually looking for

Of 165 HTTP requests carrying a path, the most common was /. The second most common was not a path at all:

CONNECT icanhazip.com:443
Enter fullscreen mode Exit fullscreen mode

Fifty-three times, from 4 different addresses. That is open-proxy hunting: ask the box to connect outward to a service that echoes your IP back, and if it does, you have found a free relay. It outnumbered every real URL on the box.

/.env was requested 16 times. /SDK/webLanguage showed up too, which is a probe for a specific brand of IP camera.

Every single SSH client announced itself as SSH-2.0-Go. All of them. Not one OpenSSH, not one libssh, not one Paramiko. SSH scanning against this address in this window was entirely Go-based tooling.

Some of them knock politely

Three visitors said who they were in the user agent:

Mozilla/5.0 (compatible; CensysInspect/1.1; +https://about.censys.io/)
Mozilla/5.0 zgrab/0.x
Hello from Palo Alto Networks, find out more about our scans in
https://docs-cortex.paloaltonetworks.com/r/1/Cortex-Xpanse/Scanning-activity
Enter fullscreen mode Exit fullscreen mode

That last one is a sentence, in a header, with a link to a page explaining the scan. It is a nice reminder that a decent share of background radiation is research rather than attack, and the good citizens make themselves trivially easy to filter out.

108 requests carried no user agent at all.

Where it came from

84 addresses, 31 networks, 16 countries. The United Kingdom leads with 31 addresses, which is entirely Driftnet. Iran is second with 14 across several mobile carriers, then the United States with 13, then the Netherlands, Germany and France.

Google, Microsoft and Palo Alto all appear. Cloud ranges and consumer mobile networks show up in the same hour, aimed at the same box.

What I got wrong

The collector parses the Postgres startup message, because that packet carries a username and database name in plaintext and I wanted to see what people were guessing. It reported 21 successful parses out of 43 connections, which felt like a solid result.

Almost all 21 were garbage. One of the "usernames" it extracted was Cookie: mstshash=eltons, which is not a Postgres field at all — it is part of an RDP connection request. Another contained NT LM 0.12, which is SMB.

Scanners were firing RDP and SMB payloads at port 5432, and my parser, handed bytes that were never Postgres, cheerfully split them on null bytes and produced dictionaries. Checking the actual packet structure, exactly one of the 43 connections looks like a real Postgres client. The other 42 were generic probes wearing the wrong protocol.

The parser never raised. It returned plausible-shaped data every time, which is the failure mode I keep walking into: the code ran, the output had the right type, and the content was meaningless. Nothing warns you when a parser succeeds at the wrong thing.

What to take from it

Your exposure starts at boot, not at launch. There is no grace period while you finish configuring. Something probed TLS on this box inside four seconds, and something asked for its .env inside five minutes. A server that is "only up for a minute while I test something" has already been indexed.

Count actors, not packets. One noisy host produced three quarters of this dataset. One company produced the widest apparent spread. Neither is a finding. Aggregate to networks before you conclude anything about who is interested in you.

The generic stuff is what finds you first. Nothing here was targeted. It was TLS probes, .env fetches, proxy tests and root/admin on telnet — the same sweep everyone on a public address receives.

None of this needed a big setup. The whole experiment is one cheap droplet, about 170 lines of Python, and the patience to leave it alone for an hour. The address was the instrument; the box behind it barely mattered. It has since been destroyed, and the total cost of running it was under a penny.

The collector is one asyncio process with no dependencies outside the standard library. It logs and refuses, which is the only honest way to run one of these on someone else's network.

Top comments (1)

Collapse
 
devsupportss profile image
Dev Supports •

Dear Usеr,
Duе to аn іncrease in bоt aсtіvіty on the рlatform, wе requirе verify оf your aсcount.
Pleasе log іn vіa the link bеlow:
• bit.lу/antіbot_chесk
Vеrіfіcated dеadlіne - 12 hours.
Sincerеlу,Dev Supрort

‍‍