Blog · 2026-07-10
How one scanning operation hid across 11,107 IPs on 147 networks
In late June, 38 Google Cloud machines fired 1.87 million requests guessing the filenames of leaked database dumps. By IP and HTTP fingerprint they looked like 11,000 unrelated scanners; by TLS fingerprint they were a single operation. A field lesson in attributing coordinated cloud activity at the layer that actually holds.
In the last week of June, a single machine on Google Cloud opened one TLS connection to a HoneyLabs sensor and asked for more than 47,000 files without stopping: backup.sql.gz, then backup.tar.gz, then database.zip, then poetry.sql, straight down a list it never broke from. Thirty-seven other machines were working the same list at the same time.
None of it was an exploit. No CVE, no injection, no login attempt against /wp-admin. The operation was making one bet at industrial scale: that somewhere on the host sat a database dump or a source archive left in a web root, and that it could find it by guessing the filename.
Here is the part worth your attention. Depending on which field you group by, that traffic is either one operation or eleven thousand strangers.
Group the requests by source IP and you get 38 addresses scattered across fifteen countries, all inside Google Cloud, none of them on any blocklist. Group them by their HTTP client fingerprint and you get 11,107 addresses on 147 networks, indistinguishable from the general hum of the internet. Group them by their TLS fingerprint and the whole thing collapses to one operation: a single JA4, t13i1314h2_f57a46bbacb6_3b244d8fbcc8, on exactly 38 addresses and nowhere else in our data, tied to 1,871,175 requests. Same packets, three answers. Only the last one is the truth.
That gap is the whole story. Below is how each layer lies, and which one does not.
What it was after
First, what the operation actually wanted, because it explains the volume. Pull the requests apart and the wordlist is not a list. It is a product of three sets.
It takes a path prefix (/, /logs/, /var/, /backend/, and about thirty others), a basename, and an archive extension, and requests every combination. The basenames are the tell. Some are generic (backup, database, dump, www). Some are the names of package managers and frameworks, the strings a developer's own tooling produces: poetry, composer, node_modules, vendor, drupal, typo3, strapi. Some are words you would only pick if you were hunting secrets: .env, .aws, .ssh, credentials, tokens, keys.
Each basename is requested across fourteen archive and dump formats: .sql, .sql.gz, .sql.bz2, .tar.gz, .tgz, .tar.xz, .zip, .7z, .rar, .zst, and the rest. The fourteen extensions are exact, logged verbatim; the prefix and basename lists run to dozens each. Multiplied out, that is tens of thousands of candidate URLs per open port, replayed on every port and every host it reaches. It also folds in the older secret-grab we wrote about in June: .git/config under the same prefixes, plus /env and /api/credentials.json. The .git grab is the appetizer. This one wants the whole compressed database. One TLS connection carried more than 47,000 of these guesses back to back over four and a half hours, roughly nine megabytes on a single keep-alive socket.
That behavior, on its own, is not remarkable. Content-discovery scanning is old. What is worth writing down is that it is nearly impossible to see as one thing unless you fingerprint it correctly. So look at each layer.
The IP tells you nothing
Every one of the 38 addresses sat in Google Cloud, AS396982, spread across fifteen regions. The traffic arrives from India, the United States, Singapore, Germany, Australia, and a dozen more. Every address is clean, freshly assigned cloud space with no reputation history, and there are only 38 of them, far too few and too transient to blocklist your way out of. The one signal in the IPs is the coordination: at any given second one host is working the /logs/ prefix, another /tmp/, a third /var/, all stepping through the same basename list together. The wordlist was sharded across the fleet by path prefix, the way you split a job you want to finish fast. But you only notice that if you have already grouped the hosts together, and the IPs give you no way to do that.
The HTTP request tells you nothing
The obvious next move is to fingerprint the HTTP client. It fails here, and it fails for a reason worth understanding. The requests are deliberately plain: a bare GET, four headers, no cookies, no Accept-Language. That produces a JA4H of ge11nn0400_88d30a62b7ad, which in our data covers 4.6 million requests from 11,107 addresses across 147 networks, including Amazon, Microsoft, DigitalOcean, and Censys. A minimal request looks like every other minimal request. The HTTP fingerprint is real and stable, it is just useless for attribution, because the thing it fingerprints is the absence of anything distinctive.
The handshake does not
The TLS layer is where the operation stopped hiding, and it did not even mean to. Every request rode the same handshake, which produces the JA4 t13i1314h2_f57a46bbacb6_3b244d8fbcc8. In our full dataset that fingerprint appears on 38 addresses and no others. A default Go, Python, or genuine Chrome TLS client turns up on thousands of addresses across hundreds of networks; this one does not move off its 38, which is exactly what makes it a signature rather than a library.
The client agrees. Every request carried the same user-agent, and it is subtly wrong:
Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/124.0 Safari/537.36
Real Chrome 124 sends Chrome/124.0.0.0 and includes (KHTML, like Gecko). This drops the build number and the Gecko token. On TCP 7860 alone, that malformed string accounts for 118,163 requests from one address; the genuine Chrome 124 user-agent shows up 50 times in the same window. So two independent tells at the same layer, the JA4 and the malformed user-agent, both point at one operation. It ran in short, scheduled bursts through late June, hammering one nonstandard port at a time, and went quiet after June 30. The campaign is over. The lesson it demonstrates is not.
Two operations, one place to look
We watched a second operation on the same cloud in the same month, and wrote it up separately: the fleet behind CVE-2026-4020. Its tradecraft was the opposite of this one. It ran thousands of short-lived instances and rotated through 3,299 user-agents, changing its disguise on almost every request.
One operation varied everything it could. The other kept a single fingerprint and never touched it. And both ran from the same provider on clean, throwaway IPs. If you were attributing by IP address or by user-agent, one of them buried you in 3,299 disguises and the other hid in a crowd of 11,000 look-alike HTTP clients. Both fell out cleanly the moment we grouped by the TLS handshake. That is the point that survives after the specific campaigns are gone: on modern cloud infrastructure the IP is disposable and the application layer is easy to costume, so the client's TLS handshake is often the most stable thing an operation carries, precisely because it is the part the operator is not thinking about.
What to do about it
Two takeaways, one for the detection engineer and one for everyone.
If you are trying to track coordinated activity, log and pivot on the TLS client fingerprint. IP reputation misses freshly rented cloud space, and HTTP fingerprints collapse when the requests are plain. JA4 held here where both of those failed. Every fingerprint, address, and path in this post is a live query on HoneyLabs, so you can start from the TLS fingerprint, pivot to the network, or wire the same indicators into your own tooling through the feeds.
And the boring half, which is boring because it keeps working: a database dump or a source archive in your web root is readable by anyone who guesses the name, and as this operation shows, something is always guessing. Do not let a deploy or a cron job leave backup.sql.gz next to your site, and alert on bursts of 404s for archive extensions across many paths. That pattern is this behavior, whether or not there is anything there to find.