Published on

訪問者の97%はボットだった ― UAを信じずに正体を5段で分類する

Authors
  • avatar
    Name
    muzzledskr
    Twitter

個人ブログのアクセスを 9 日ぶん、1 リクエスト 1 行の生ログで貯めて、全 IP を「それが何者か」で分類した。結果、1198 IP のうち人間候補は 31 個(2%)。残り 97% はボットだった。

この記事は、その分類をどういう基準で・なぜその順番でやったかの記録である。集計だけ見ていると分からず、そして UA(User-Agent)を証拠に使うと必ず間違える、という話でもある。

TL;DR

  • 全 1198 IP を排他的に分類 → ボット 97% / 人間候補 2%
  • UA は自己申告。単独では証拠にならない。 データセンターから来る「iPhone MobileSafari」が 297 IP、ブラウザ UA を名乗りながら JS/CSS を一切読まないスクレイパーが 76 IP
  • 単一シグナルでは足りない。検証済みボット判定だけでは 48% しか捕まらない
  • 判定は 5 段。ASN(接続元ネットワーク)と行動(アセットを読んだか)を重ねて初めて 97% に到達する

前提:なぜ生ログなのか

Cloudflare の集計 API(httpRequestsAdaptiveGroups)は「どのステータスが何件」までしか答えない。「その IP が何者か」は分からない。そこで生ログ(httpRequestsAdaptive、無料プランでも GraphQL から取れる)を 15 分ごとに S3 へ NDJSON で貯め、DuckDB で読む。この構成自体は別記事に書いた。

生ログには 1 行ごとに clientIP / userAgent / clientASNDescription(接続元ネットワーク)/ clientRequestPath / verifiedBotCategory(Cloudflare が検証したボット種別)が入る。この 5 列で分類する。

UA を証拠にしてはいけない

分類基準の話に入る前に、最初に捨てるべき前提を先に潰す。「User-Agent を見ればブラウザか判別できる」——これは誤りだ。UA はクライアントが自己申告する文字列で、いくらでも詐称できる。

実データで見せる。分類の結果、データセンター(AWS・Alibaba・OVH 等)から来た IP が名乗っていた UA はこうだった。

UAIP 数
iOS / MobileSafari297
Windows / Chrome69
MacOSX / Chrome9

iPhone の Safari が、データセンターから 297 個。 iPhone はデータセンターのラックには無い。この UA は嘘である。

逆に、ブラウザ UA を名乗りながら JS/CSS を一切読まない(=ページをレンダリングしていない)IP も 76 個いた。

名乗った UAIP 数
Windows / Chrome22
Android / ChromeMobile14
MacOSX / Chrome4

Chrome を名乗るなら、ページを開けば必ず /_next/static/ の JS バンドルと CSS を取りに行く。それが 0 なら、Chrome の顔をした何かだ。

私は実際、この詐称に一度引っかかった。別の調査で www の 522 エラーを踏んでいた IP 群を見て「MobileSafari だから実ユーザーだ」と判断し、後で撤回している。UA を見た瞬間に負けていた。

分類:5 段を順に当てる

そこで、UA を「参考情報」に格下げし、より詐称しにくいシグナルを軸に据える。上から順に当てはめ、最初に当たった段で確定する(排他的分類)。順番に意味がある。

① 検証済みボット — Cloudflare が正体を確証している

verifiedBotCategory が空でない IP。Googlebot や Bingbot、SNS のリンクプレビュー(Facebook / Twitter)など、Cloudflare が逆引きや署名で正体を確認済みのボット。これは詐称ではない(Cloudflare 側で検証しているため)。

585 IP(48%)。 ここで半分が確定する。だが逆に言えば、検証済みボット判定だけでは残り半分を取りこぼす。

② UA が自白している

userAgentbot crawl spider curl wget python go-http okhttp 等を含む IP。①で検証されなかったが、自分でボットだと名乗っているもの。隠す気のないスクレイパーや自作クローラー。

74 IP(6%)。

③ 攻撃者

/.env /.git/config /.aws/credentials /wp-admin/ xmlrpc.php /vendor/phpunit/ など、正規の訪問者が絶対に踏まないパスを叩いた IP。認証情報や CMS 脆弱性の探索。

26 IP(2%)。 このサイトは Next.js で WordPress も PHP も使わないので、これらは 100% 悪意である。

④ データセンター発 ★ここが本命

①〜③に当たらず、接続元 ASN がクラウド/VPS 事業者の IP。Amazon・Google・OVH・Hetzner・Alibaba・各種ホスティング。

406 IP(33%)。 これが最大の隠れ層だった。UA は正常なブラウザを名乗る(前述の「データセンターの MobileSafari」がこれ)。だから UA だけ見ると人間に見える。ASN を見て初めてボットと分かる。

人間は家庭用 ISP か携帯キャリアから来る。データセンターから「ブラウザ」で来るのは、ヘッドレスブラウザによるスクレイピングか、各種の自動巡回だ。

⑤ アセット未取得 — ブラウザ UA を騙る

①〜④に当たらず、ブラウザ UA を名乗るのに /_next/static/ を 1 つも取得していない IP。76 IP(6%)。 ④をすり抜けた(住宅 IP を使う)スクレイパーがここで残る。

⑥ 人間候補

上のどれにも当たらず、アセットを取得している(=実際にページをレンダリングした)IP。31 IP(2%)。

結果

分類IP 数割合
検証済みボット58548%
UA が自白746%
攻撃者262%
データセンター発40633%
アセット未取得(UA 詐称)766%
人間候補312%
ボット計116797%

個人ブログのトラフィックの 97% はボット。人間は 2%。

そして重要なのは各段の効き方だ。①だけでは 48% しか取れない。 ④のデータセンター発 33% は、UA が正常なので UA では絶対に落とせない。⑤の 6% は UA を騙る。「1 つのシグナルで判定」は、この時点でどう転んでも過半数を取りこぼす。

なぜ順番が要るのか

排他的に、上から当てる設計にしたのは、シグナルが重なるからだ。あるスクレイパーは「データセンター発」かつ「UA 詐称」かつ「攻撃パスも叩く」ことがある。もし各基準を独立にカウントすると同じ IP を三重に数えてしまう。

順番は確度の高い順にした。①(Cloudflare が検証済み)が最も確実で、⑥(消去法で残った人間候補)が最も弱い。強い証拠から先に確定させ、残りを次の段に送る。これは監視のフィルタ設計そのもので、誤検知を避けたい判定は確度順に並べるのが定石だ。

副産物:WAF ルールが安全に書ける

分類③(攻撃者)を抽出したついでに、正規ボット(①の 585 IP)が攻撃パスを叩いた回数を数えたら、0 件だった。Googlebot が /.env を取りに来ることはない。

つまり「攻撃パスをブロックする」WAF ルールは、正規ボットを 1 件も誤爆しないことがデータで保証できる。感覚で WAF を書くと検索エンジンを弾いて SEO を壊すが、先にログを分類しておけば、それが起きないことを事前に確認できる。

まとめ

  • UA は自己申告。証拠に使うと、データセンターの iPhone を実ユーザーと数える。
  • 判定は複数シグナルの重ね合わせ。単一では過半数を取りこぼす。ASN と行動(アセット取得)が UA より強い証拠になる。
  • 個人ブログでも訪問者の 97% はボット。GA が見せる「ユーザー数」は、この 2% の、さらに一部(計測ビーコンに乗った分)でしかない。

判定ロジックは全部 SQL で書ける。件数はすべて上に出した実数で、再現できる。

一次情報