HoneyLabs
26 machines using 42,321 user-agents to spoof AI crawlers

Blog · · HoneyLabs

26 machines using 42,321 user-agents to spoof AI crawlers

The scanners spoofing AI crawler user-agents to hunt credentials turned out to be one HTTP client running on 26 addresses. On 25 August it switched from nine fixed strings to a generator that has produced 6,648 different spellings of Claude-User alone, which breaks every allow-list that matches on the name.


On 25 August one of our sensors started seeing a lot of Claude.

However, not one Claude crawler, but four thousand nine hundred and six of them, in a single day, each announcing itself with a slightly different user-agent string. On every previous day we had seen this client, going back to 11 August, there had been nine.

Nine is around what you'd expect, as there are only so many AI crawlers and each of them uses a settled user-agent. What arrived on 25 August was a generator, and following it back turned out to be worth more than the generator itself.

Distinct user-agent strings per day from one client, flat at nine through 23 August, then 4,906 on 25 August and thousands every day after

One client, 42,321 names

Every one of those strings carries the same JA4H fingerprint, ge11nn05en_813e32c09d15, which is computed from the shape of an HTTP request instead of its contents. The fields are the method, the HTTP version, whether a Cookie and a Referer are present, how many other headers there are, the first two characters of Accept-Language, and a hash of the header names in the order they arrived. Note that the count excludes Cookie and Referer, which is why the two POST profiles below carry eight and ten headers but encode 07 and 09. It describes the client library, so it survives precisely the thing this operator spends its effort on.

Between 2 August and 6 September that fingerprint sent 105,438 requests from 26 addresses, 25 of them on Google Cloud (AS396982), carrying 42,321 distinct user-agent strings between them. It is also most of the category, since 121,540 requests across our whole dataset have claimed to be an AI crawler and 86.8 percent of them carry this one fingerprint.

Building a spoofed string

The generator grafts a real crawler token onto a randomly generated browser string, so that four of the 6,648 Claude-User variants share only their middle:

Mozilla/5.0 (X11; Linux x86_64; rv:143.11) Gecko/20100101 Firefox/143.11;
  compatible; Claude-User/1.0; +Claude-User@anthropic.com
Mozilla/5.0 (Windows NT 10.0; rv:132.12) Gecko/20100101 Firefox/132.12;
  compatible; Claude-User/1.0; +https://www.anthropic.com/claude-user
Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible;
  Claude-User/1.0; +Claude-User@anthropic.com
Mozilla/5.0 (iPhone; CPU iPhone OS 18_4 like Mac OS X)
  AppleWebKit/605.1.15 (KHTML, like Gecko; compatible; Claude-User/1.0;
  +https://www.anthropic.com/claude-user) Version/17.0 Mobile/15E148 Safari/604.1

Diagram of a spoofed user-agent string in three parts: a generated browser prefix carrying an invented Firefox 143.11, the untouched crawler token Claude-User/1.0, and one of two contact suffixes. Only the token is constant between requests.

Three things vary independently, being the browser prefix, the placement of the parenthesis and semicolon around compatible, and which of two contact suffixes follows the token, while the crawler token itself is left alone because the token is the part that has to be recognised.

Horizontal bar chart of distinct user-agent variants per crawler token from one client: Claude-User 6,648, GPTBot 4,532, OAI-SearchBot 4,502, GrokBot 4,365, Perplexity-User 4,331, ChatGPT-User 4,268, Amazonbot 2,361, ClaudeBot 2,336, Google-Extended 2,287, PerplexityBot 2,255, Amzn-SearchBot 2,222, Bytespider 2,214.

Anthropic does not publish a user-agent string for these at all. It documents three bot names, which is what a robots.txt line matches on, and points site owners at a published list of its crawler addresses for verification. One name, and we logged 6,648 different strings carrying it.

The Firefox prefixes carry 200 distinct version strings, of which 190 have a minor component that is not .0, among them 78.10, 105.9, 121.18, 143.11 and 149.9. Firefox never puts a minor version there, since point releases like 152.0.6 still send Firefox/152.0, so a .10 or a .18 is already wrong.

The rv: token is the better tell, and it is the one the generator gets exactly backwards. Mozilla froze rv: at 109.0 for Firefox 110 and later, because sites checking for rv:11 were misreading rv:110 as Internet Explorer 11. A genuine Firefox 143 therefore announces itself as rv:109.0) Gecko/20100101 Firefox/143.0, with the two numbers deliberately disagreeing. This generator makes them agree, which is what Firefox did until three years ago. Of the 6,926 Firefox-flavoured variants here, not one carries rv:109.0, and all 5,521 that claim Firefox 110 or newer have an rv: that a real build of that version would never send.

Following the fingerprint back

A fingerprint only tells you where to look next. The useful move is to follow it sideways, taking the addresses that produced it and asking what every other fingerprint those same addresses emit looks like. Doing that turns one client into something considerably less tidy.

Those addresses run four distinct client profiles, and the address sets of the other three sit almost entirely inside the first: 24 of 24 for the metadata module, 16 of 16 for the JSON-bodied one, and 23 of 24 for the GraphQL module, whose single outlier is an Alibaba Cloud host that sent two requests. That degree of overlap is not something chance arranges. All four spoof AI crawler names, between 62 and 68 percent of their requests each, so every part of the toolkit wears the same disguise.

Profile Shape First seen on these addresses What it does
ge11nn05en_813e32c09d15 GET, 5 headers 11 Aug credential and config files
ge11nn0600_3e3d7a122305 GET, 6 headers 16 Aug SSRF at cloud metadata
po11nr0700_7963de0d52cd POST, 8 headers 20 Aug GraphQL introspection
po11nr0900_e84d48e0c8fb POST, 10 headers 28 Aug SSRF with a JSON body

Table of four HTTP client profiles run from the same address set: ge11nn05en, five headers, 11 August, credential and config files, 158,062 requests from 25 addresses; ge11nn0600, six headers, 16 August, SSRF at cloud metadata, 20,235 requests from 24 addresses; po11nr0700, 20 August, GraphQL introspection, 1,205 requests from 23 addresses; po11nr0900, 28 August, SSRF with a JSON body, 1,724 requests from 16 addresses.

Read down the date column and you are watching someone build a tool in production, against the whole internet, one module a week. Those dates are when each profile first appeared on this address set rather than when the fingerprint first existed anywhere, since two of the four are shapes other software also produces and go back months in our data on unrelated hosts.

What the quieter modules are for

The credential-file module is the loud one and the least dangerous. The two SSRF modules are the point of the exercise, and they sweep a matrix rather than guessing: fourteen endpoints that a preview, screenshot or webhook service might expose, crossed with seven names for the parameter that takes a URL.

endpoints   /fetch /proxy /redirect /image /preview /screenshot /webhook
            /api/fetch /api/proxy /api/image /api/preview /api/download
            /api/webhook /api/v1/fetch
parameters  url path dest uri target src file

Every combination is pointed at the same three places, which are the AWS instance metadata service at 169.254.169.254/latest/meta-data/iam/security-credentials/, then file:///root/.aws/credentials and file:///proc/self/environ through the identical parameter when the fetcher will accept a file:// scheme. The POST variant carries the same targets as JSON:

{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}   867
{"url":"file:///root/.aws/credentials"}                                       214
{"url":"file:///proc/self/environ"}                                           213

The GraphQL module asks every endpoint it can think of for a full schema dump, using the standard introspection query against /graphql, /v1/graphql, /api/graphql and /graphql/console, which is reconnaissance for whatever comes after.

So the AI crawler names are the costume. Underneath is a cloud credential thief that would quite like to read your instance metadata, and it wears the costume because a growing number of sites now treat a crawler name as a reason to be more permissive.

What gives the whole thing away

Reading the raw headers produces two signatures that are cheaper and far more reliable than counting Firefox versions.

The first is that the Host header carries a bare IP address in 167,777 of the 167,780 requests this fingerprint sent us, with only three naming a hostname. A real crawler arrives because it followed a link and therefore always has a hostname, whereas this arrives because it is walking address space.

The second has no benign explanation at all. Two of the four profiles send Metadata-Flavor: Google and Metadata: true on every request, 21,959 times from 24 addresses, including to ordinary web servers that have nothing to do with cloud metadata. Those headers exist to satisfy the GCP and Azure metadata endpoints, and the tool sets them globally in the hope that something downstream forwards them. Nothing legitimate sends them to your webserver.

The header set itself is assembled from mismatched parts. Accept-Encoding: gzip alone is what Go's standard library sends when the caller sets nothing, the Accept value is the long-standing Firefox form, and the Accept-Language of en-US,en;q=0.9 is the Chrome default rather than the q=0.5 Firefox uses. No single browser produces that combination, and the same five header keys arrive in the same order across all 167,780 requests.

Prior reporting

GreyNoise documented this behaviour on 28 August. Between 28 July and 23 August they observed six spoofed crawler names belonging to four companies arriving on one HTTP client fingerprint, from 824 addresses spread across 795 separate /24 networks, and noted that the fingerprint identifies the software making the requests rather than the machine running it. They recorded that /robots.txt was never requested under those six names, and measured Anthropic's real crawler over the same window as a control, where /robots.txt was the most requested path at 12 percent of its traffic and no credential file was requested at all.

Our window runs later and our vantage point is narrower. What is new here is the rest of the operator's toolkit, the structure of the user-agent generator, and what changed after 23 August.

Through 23 August this client used nine fixed strings, which is the period their measurement covers. Two days later it began producing thousands a day, so the 6,648 spellings of Claude-User above belong to a later stage of the operation than the traffic in their report.

Failing matching strategies

Every current scheme for controlling AI crawlers keys on the user-agent string, whether that is a robots.txt directive naming an agent, Cloudflare's AI crawler controls, the various pay-per-crawl proposals, or a hand-written nginx rule.

Against this generator both matching strategies fail in opposite directions, because exact matching catches only the two well-formed spellings in our data and misses the other 6,646 Claude-User variants, while substring matching on Claude-User catches all of them and then grants the spoofer whatever the real crawler was going to get, which for a growing number of sites is a lighter rate limit or a pass through the bot gate.

The check that does hold is forward-confirmed reverse DNS, where the address has to resolve to a hostname under a domain the operator controls and that hostname has to resolve back to the same address. It is the method Google and Bing both document, and it is the only test discussed here that cannot be satisfied by editing a string, because it asks a question of the address rather than of the client.

Limitations

Our sensors are honeypots that host nothing a legitimate crawler would want, so the absence of real AI crawler traffic in this data is expected and says nothing about the ratio on a normal website, and what our vantage point supports is the shape of the spoofed traffic rather than its share of anyone else's logs.

The fingerprint is also a weaker signal on its own than it looks, since ge11nn05en describes a GET over HTTP/1.1 with five headers, no cookie and an Accept-Language, which other tools produce too. What makes this cluster coherent is the combination of one fingerprint family, one network, one wordlist, a user-agent generator with a recognisable grammar, and four profiles whose address sets nest almost perfectly, at 24 of 24, 23 of 24 and 16 of 16.

Two further fingerprints appear from these addresses and are deliberately excluded here, because they are generic shapes that millions of unrelated requests share and only a couple of hundred of ours came from this operator. And 26 addresses is only our slice, since we see what reaches our own sensors and nothing else.

For your logs

Four checks, in order of how cheap they are to run.

Anything sending Metadata-Flavor: Google or Metadata: true to your webserver is hostile, with no exceptions worth carving out.

A Host header containing a bare IP address, from a client claiming to be a major crawler, means the client found you by scanning rather than by following a link.

Any user-agent claiming Firefox/110 or newer whose rv: is not 109.0 is not Firefox, and a Firefox/<major>.<minor> with a non-zero minor is not Firefox either. Both hold whatever crawler name is bolted on beside them.

Anything claiming to be a major crawler that has never requested /robots.txt is not crawling, whatever else it may be doing, which GreyNoise also recorded, and which holds across our 105,438 requests.

Then verify by address rather than by string. We checked all 26 of these addresses against every published crawler range we could find, which is Anthropic's list, OpenAI's three for GPTBot, ChatGPT-User and OAI-SearchBot, Google's three, and Perplexity's two. That comes to 1,136 IPv4 prefixes, and not one of the 26 appears in any of them. Amazon, ByteDance and xAI publish no machine-readable range file that we could locate, so the Amazonbot, Amzn-SearchBot, Bytespider and GrokBot names cannot be settled the same way; those requests come from Google Cloud, which is not where those crawlers run, but that is circumstantial rather than a list check.

Do not shortcut any of this into blocking the network. 18 of Anthropic's own 26 prefixes sit on Google Cloud, the same place these requests come from, so an operator who blocks AS396982 loses the real crawler and keeps the problem. The address either appears on the vendor's list or it does not, and forward-confirmed reverse DNS covers the crawlers that publish no list.

The live fingerprint page is at ge11nn05en_813e32c09d15 and updates as the client moves. Method and redaction.

New research, by email

Get new HoneyLabs research by email as it publishes: write-ups like this one, plus the threat reports on which ports moved, which KEV and recent CVEs are being probed in the wild, and the attack paths worth grepping your own logs for. Here is a recent threat report.

Double opt-in: we send one confirmation email and nothing else until you click it. Unsubscribe in one click, any time.