スマートフォン・タブレットからインターネットサーバーオペレーション

APPW.jp
 
bot classification / reverse dns

botは、3種類いる
UAは自己申告にすぎない

ログを見ていて気づきました。Googlebot と、/wp-login.php を叩くスキャナを、 同じ「bot」として数えていた。弾いてよいものと、絶対に弾いてはいけないものが、 ひとつの数字に埋もれていたのです。役割で分ける設計と、 「Googlebot と名乗るだけで除外される」という穴を塞いだ話です。

User-Agent 逆引きDNS Googlebot MongoDB

[ § 0 ] 導入

「bot」を、一括りにしていた

うちのログモジュールは、bot だと判断したら bot=1 を立てます。それだけでした。 集計すると1日あたり2,000件を超えます。数として眺めているぶんには、それで足りていました。

ところが中身を見ると、性質がまるで違うものが混ざっています。 Googlebot と、/wp-login.php/.env を順に叩く python-requests が、同じ箱に入っている。

片方は絶対に弾いてはいけない相手で、もう片方は弾きたい相手です。 同じ数字で数えていては、どちらの判断もできません。

[ § 1 ] 分類

役割で、3つに分ける

分類性質扱い実測
正規クローラ検索エンジン・SNSのプレビュー取得絶対に弾かない212
攻撃脆弱性探索・偽装スキャナ遮断候補1,206
その他(グレー)SEOツール・正体不明判断が要る978

分けてみると、攻撃が全体の55%を占めていました。そして 正体不明のグレーが45%近くある。この3つ目がいちばん扱いに困る、というのが後で分かります。

[ § 2 ] 正規

弾いたら、終わり

Googlebot を遮断すれば、いずれ検索結果から消えます。 アクセス解析の数字が下がってから気づく類の事故で、取り返しがつきません

だから分類器の設計は、「攻撃を見つける」ではなく「正規を守る」から始めるべきでした。 最初に正規クローラのリストを置き、そこで確定させ、以降のルールに触れさせない。 この順序が、後で効いてきます。

SNS のプレビューも同じ

Twitterbot や Discordbot、LINE のリンクプレビュー取得なども、弾けば「共有したときに画像が出ない」という形で影響が出ます。検索エンジンほど致命的ではありませんが、気づきにくい分だけ厄介です。

[ § 3 ] 攻撃

実装の粗さが、痕跡になる

攻撃側は、いくつかの形で自分を明かします。いちばん確かなのは何を叩いたかです。 /wp-login.php/.env/actuator/env—— UAを見るまでもなく、探索の意図がパスに出ます。

面白いのは、そこから先です。ツールの実装の粗さが、UAに残っていることがあります。

綴りを間違えている

実際に記録されていたUA
Mozlila/5.0 (Linux; Android 7.0; SM-G892A Bulid/NRD90M; wv) AppleWebKit/537.36
^^^^^^^                              ^^^^^
Mozilla → Mozlila                    Build → Bulid

2箇所とも綴りが違います。 本物のブラウザがこの文字列を送ることはありません。UAをコピペで作った痕跡です。

UAが途中で切れている

記録されたUA と、本物の Safari
# 記録されたもの
Mozilla/5.0 (iPhone; CPU iPhone OS 17_2 like Mac OS X) AppleWebKit/605.1.15 (KHTML

# 本物の Safari はこう続く
... AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.2 Mobile/15E148 Safari/604.1
                              ↑ 最初のカンマで切れている

本物のUAを最初のカンマで切ると、記録された文字列と完全に一致しました。 UAのリストをカンマ区切りで読み込み、引用符の処理を忘れたまま分割した—— そういうツールの痕跡です。

これらは、設定では隠せません。 IPは変えられます。UAも書き換えられます。でも 作った人間の雑さは、出力に残ります。 相手のミスが、こちらの検出条件になる——数少ない、防御側に有利な材料です。
[ § 4 ] グレー

いちばん難しいのは、3つ目

SEO分析ツール、AI学習用のクローラ、正体不明のスクレイパー。 害があるとも無いとも言い切れない相手が、実測で978件ありました。

判断の材料になるのは、このあたりです。

見るところ意味
robots.txt を読むか作法を守る意思があるか
頻度は妥当かサーバーへの負荷を考えているか
UAに連絡先URLがあるか身元を明かす気があるか
攻撃パスを叩くか探索の意図があるか
「決めない」という選択も有効です。 無理に白黒つけず、監視だけして放置する。 害が出てから対処しても間に合う相手が大半です。 判断を保留できる箱を用意しておくこと自体が、設計の一部だと思うようになりました。
[ § 5 ] 順序

攻撃判定を先にすると、Googlebotが消える

「ブラウザを名乗るbotは怪しい」——これは妥当なルールに見えます。 実際、偽装スキャナの多くは Mozilla/5.0 (Macintosh... のような ブラウザそっくりのUAを使います。

ところが、このルールを先に評価すると事故が起きます。

正規クローラのUA
# Googlebot スマートフォン版
Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36
  (KHTML, like Gecko) Chrome/150.0 Mobile Safari/537.36
  (compatible; Googlebot/2.1; +http://www.google.com/bot.html)

# Applebot
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/605.1.15
  (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15
  (Applebot/0.1; +http://www.apple.com/go/applebot)

どちらもブラウザとして始まります。 「Linuxを名乗る」「Macintoshを名乗る」というパターンに、 正規クローラが真っ先に引っかかります

だから順序はこうでなければなりません。正規の判定を最初に置き、そこで確定させる。 該当したら、以降のルールには一切触れさせない。デモで両方を試せます。

[ § 6 ] 適用範囲

パターンは、文脈なしでは使えない

ここは実装していて肝を冷やした部分です。 検証のために本物の Chrome を分類器に通してみました。

本物のChrome を判定させると
Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 ...

→ 判定:攻撃(Windowsを名乗る)

当然です。ブラウザなのだから、ブラウザを名乗ります。 このパターンを全トラフィックに当てたら、訪問者全員を遮断します

実装では事故が起きません。この分類を bot=1 と判定済みのレコードにしか適用していないからです。 裏を返せば——「ブラウザを名乗るのが怪しい」のは、 すでに何らかの理由でbotと判定された集合の中でのことでした。

判定条件は、適用範囲とセットで初めて意味を持ちます。 パターンだけを他所からコピーしてくると、前提が抜け落ちます。 「どういう集合に対して、この条件は成り立つのか」—— 条件そのものより、そちらを書き残しておくべきでした。
[ § 7 ] 自己申告

UAは、誰でも名乗れる

ここまでの設計には、大きな穴がありました。 正規クローラの判定を、UA文字列だけで行っていたことです。

UAは自己申告です。検証されていません。 Googlebot と書いて送れば、こちらのホワイトリストを通過して除外されます。 「正規を守る」ために置いた最優先のルールが、最も緩い判定になっていました

逆引き → 正引きの往復で確かめる

Google も Bing も、公式に検証方法を案内しています。2段階です。

往復確認
# ① IPから名前を引く(PTRレコード)
$ dig -x 66.249.66.1 +short
crawl-66-249-66-1.googlebot.com

# ② その名前を正引きして、元のIPに戻るか
$ dig crawl-66-249-66-1.googlebot.com +short
66.249.66.1   ← 戻った。本物

②が肝です。 PTRレコードはIPの持ち主が自由に書けます。 攻撃者が自分のIPのPTRに crawl-1-2-3-4.googlebot.com と書くことは可能。 しかし googlebot.com のゾーンは Google しか触れないので、 正引きで元のIPに戻すことはできません

実装したら、実際に見つかった

cron で検証してMongoDBに貯める仕組みを入れました。数日で結果が出ました。

逆引き結果名乗り正引き
…ips.acedatacenter.comGooglebot不可
…ips.acedatacenter.comGooglebot不可
…ips.acedatacenter.comGooglebot不可
…ips.acedatacenter.comGooglebot不可
…ips.acedatacenter.comGooglebot の一語だけ不可
…host.colocrossing.comGooglebot不可

7日間で6件。 すべてホスティング事業者のIPでした。 しかも全件とも正引きすらできません—— PTRに名前を書いただけで、対応するAレコードを用意していない。身元を繕う努力すらしていません。

注目したのは分布です。1つの /24 に3件集中していました。 個別のIPを弾くより、サブネット単位で止めたほうが効率が良い。 そして5件目の名乗りが Googlebot の一語だけというのも印象的でした。 UA文字列を整える手間すらかけていない。それでも、従来の実装なら通過していたのです。

[ § 8 ] 誤検出

今度は、こちらが疑いすぎた

検証を入れて満足していたら、詐称リストに身に覚えのないものが混ざりました

詐称と判定されたもの
IP  : 147.92.179.117
PTR : poker-117.line-apps.com   ← 正引きも一致する
UA  : facebookexternalhit/1.1;line-poker/1.0

LINEのリンクプレビュー取得でした。 line-apps.com は正当なドメインで、往復確認も通ります。 facebookexternalhit 互換のUAを使うのは、 OGタグを返してもらうための一般的な作法です。

「検証できない」と「偽物」は違う

原因は私の設計ミスでした。ホワイトリストに14種類のクローラを入れたのに、 逆引きで検証できるドメインは一部しか登録していなかったのです。

事業者検証方法逆引きで判定
Google / Bing / Apple / Yandex / Baidu公式に逆引きを案内できる
Slackbot / Discordbot / LINE などIP範囲の公開など、別の方式できない

登録していないドメインは、一致するはずがありません。 それを「詐称」と呼んでいた。正しくは「こちらに検証手段が無い」だけです。 相手の問題ではなく、こちらの限界でした。

だから判定は3値にしました。 本物(往復一致)/詐称(検証方法が確立している事業者を名乗り、一致しない)/ 保留(DNSが応答しない、または検証手段が無い)。 「わからない」を、そのまま「わからない」として持つ。 2値に押し込めようとしたのが、そもそもの間違いでした。
[ § 9 ] 体感デモ

分類器を、動かしてみる

実際に観測したUAを含む9種類を、分類器に通します。 評価順序適用範囲を切り替えて、何が起きるか見てください。

Interactive · bot classifier
順序と範囲で、結果が変わる
逆引きの結果は、実際に確認した値を用いています(本物は往復一致、詐称は不一致)。

「攻撃パターンを先に」を選ぶと、Googlebotスマホ版と Applebot が攻撃判定になります。 さらに偽Googlebot は素通りします——UAが本物と同一なので、 攻撃パターンには引っかからず、次の正規判定で除外されてしまう。 攻撃を先に見ても、詐称は防げません

適用範囲を「全トラフィック」にすると、本物のChromeまで攻撃判定になります。 パターンは、それが成り立つ集合の上でしか意味を持ちません。

[ § 10 ] まとめ

守るものから、設計する

botを分類する仕事は、「攻撃を見つける」問題ではありませんでした。 弾いてはいけないものが混ざっている以上、まず正規を守り、 そのうえで攻撃を見つける——順序が逆だと、守るべきものから先に壊れます。

そして、守るための判定こそ厳しく検証すべきでした。 「Googlebot と名乗れば除外」というルールは、攻撃者がいちばん簡単に悪用できる穴です。 最優先で置いたルールが、最も緩かった。

この記事のまとめ

  • botは役割で3つに分ける。正規・攻撃・グレー。同じ数字で数えない。
  • 設計は「攻撃を見つける」ではなく「正規を守る」から始める
  • 評価順序が命。攻撃判定を先にすると、Googlebotスマホ版とApplebotが引っかかる。
  • 判定条件は適用範囲とセット。全トラフィックに当てれば、本物のChromeも攻撃になる。
  • UAは自己申告。逆引き→正引きの往復で確かめる。PTRは相手が書けるが、ゾーンは書けない。
  • 実装したら7日で6件の偽Googlebot。全件ホスティング事業者、正引きすら不可。
  • 「検証できない」と「偽物」は違う。判定は3値にして、わからないものは保留する。
  • 実装の粗さ(綴り誤り、切れたUA)は隠せない。相手のミスが手がかりになる

関連記事

『botは、3種類いる — 正規・攻撃・グレー、UAは自己申告にすぎない』を公開しました。