対象:検索などの機能が外部APIを呼んでいるサイトの運営者。本記事は、当サイトの運営者が関わるサイトで実際にあったことを一般化したものです。サイト名、外部サービス名、具体的な上限値や日付は含めていません。攻撃の手順は扱いません。
何が起きたか
当サイトの運営者が関わるサイトで、検索機能が検索のたびに外部APIを呼んでいました。数日のうちに検索のアクセスが通常の約20倍になり、外部APIの同時実行数の上限に当たって、検索の大半(9割以上)が503を返すようになりました。
最初の見立ては「IP単位の上限(1分にN回まで)で収まる」でした。これは誤りでした。
検索のアクセスが数日で約20倍になった
外部APIの同時実行数の上限に当たり、検索の大半が503になりました。この時点では、単に「エラーが増えている」ように見えました。
最初の見立て:IP単位の上限で収まる(誤り)
1分あたりの回数をIPごとに数えていれば、特定のIPが大量に呼ぶ形のアクセスは抑えられます。しかし、今回はそういうアクセスではありませんでした。
計測して分かったこと
ある1日の約6万件の検索は、約6万の別々のIPから来ていました。つまり1IPあたりほぼ1回です。住宅用の回線を中継に使うプロキシのような形で、UAは普通のブラウザ、Refererは自サイト、検索語は実在の商品名でした。価格の収集が目的だったとみられます。
外部APIの1日の枠が早い時間に尽きた
外部APIの枠は、ボットの検索で早い時間に使い切られました。通常の訪問者は、1日の大半で検索できない状態でした。
「問題」に見えなかった理由
外部APIの枠は守られていたため、外部APIの側は正常で、サイトの側は「エラーが増えているだけ」に見えました。枠を守ることと、訪問者が使えることは別でした。
想定していた形:少数のIPが大量に呼ぶ
IP単位の上限に当たる → 止まる ✓
実際の形:約6万のIPが各1回ずつ
どのIPも上限に届かない → 外部APIの枠だけが減っていく
= 数える単位がIPである限り、この形は見えない
「上流の枠が守られている」ことと「訪問者が使える」ことは別です
外部APIの枠や同時実行数を守る仕組みは、外部APIを壊れさせないためのものです。それが働いている間、外部APIの側のエラーは出ません。その代わりに、枠を使い切られた側(自サイトの訪問者)が不便を被ります。
保護が働いていることが、問題が見えにくくなる原因になりました。見るべきなのは、実際の訪問者が検索に成功した割合です。
どう止めたか
止まらなかった設計
検索のリクエストが来たら、誰からでも外部APIを呼ぶ。制限はIP単位の回数だけ。分散したアクセスは、1IPあたりの回数が少ないため、制限に触れません。
止めた設計
サイト自身のページが発行した署名付きトークン(Cookie)がある検索だけ、外部APIを呼ぶ。トークンなしは軽いページか403を返す。さらに、全体とトークン別に1時間あたりの上限を置く。
- 署名付きトークン
- サイトのページが発行するトークンを、外部APIの呼び出しの条件にした。トークンがなければ軽いページか403を返す
- 集計の保護
- トークンなしの検索は、人気検索のランキングから除外した。ボットの検索語で、ランキングが実態から離れていたため
- 1時間あたりの上限
- 全体の上限と、トークン別の上限の両方を置いた
公開した直後に、503は0になりました。トークンのない検索は1日に約10万件が403になり、通常の訪問者の検索は通るようになりました。
学んだこと
- IP単位の上限は、分散したボットには効かない。1IPあたり1回のアクセスは、どんなに上限を低くしても数えられない。
- 上流の枠が守られていることは、利用者が使えることを意味しない。保護は、問題を見えにくくすることがある。
- 人気検索のような集計は、ボットのアクセスで汚れる。外部APIを守る対策と同時に、集計の対象も絞る必要がある。
守る側がやること
当サイトの考えとして、次の順で入れることを勧めます。これは本記事の一次情報ではなく、当サイトの運営者の判断です。
高い処理の前に、サイトが発行したトークンを確かめる
外部APIの呼び出しなど、費用や枠を消費する処理の前に、サイトのページが発行した署名付きのトークンを確かめます。トークンは有効期限を付け、サーバー側で検証します。ない場合は、重い処理に進まず、軽いページか403を返します。
トークンなしのアクセスを集計から外す
人気検索、検索語の統計、おすすめの表示は、トークンのある検索だけで作ります。ボットの検索語が、自サイトの表示に混ざる事態を避けられます。
全体とトークン別の上限を置く
IP単位ではなく、全体の1時間あたりの上限と、トークン別の上限を置きます。全体の上限は、外部APIの枠を使い切らないための最後の歯止めです。
結果のキャッシュと待ち行列を組み合わせる
同じ検索語の結果は、外部APIを呼び直さずに返せるようにします。混んだときは、待ち行列に入れて順番に処理します。1つの手段が破られても、枠が一気に尽きにくくなります。
訪問者の成功率を見る
外部APIのエラー率だけでなく、実際の訪問者が検索に成功した割合を記録し、下がったら通知を受け取ります。組織のセキュリティ基準でいう、異常に気づける状態を作ります。
トークンは認証ではなく、攻撃側の費用を上げる手段です
トークンは認証ではありません。トークンが与えるのは「何も考えずに検索のURLだけを大量に呼ぶ」アクセスを止める効果で、完全な防御ではありません。
だから、キャッシュ、待ち行列、トークン別の上限と組み合わせます。当サイトの考えとして、ボットが毎回新しいトークンを取得するならトークン別の上限は効かないため、全体の上限とキャッシュを必ず併用してください。当サイトの見方は、レート制限と濫用対策でも書いたとおり、成功率をゼロにするのではなく、攻撃側にとって割に合わなくすることです。
出典(一次情報)
- OWASP API Security Top 10 2023「API4:2023 Unrestricted Resource Consumption」 — api-security.owasp.org(クライアントが一定時間に操作できる回数を制限すること、サードパーティサービスの支出上限の設定)
- OWASP Automated Threats「OAT-011 Scraping」 — owasp.github.io(スクレイピングの定義:価格設定などに使うためにコンテンツやデータを収集すること。認証のないデータも対象になる)
次に読む
- 設計:レート制限と濫用対策:総当たり攻撃と過剰な課金を防ぐ設計
- 前提:X-Forwarded-For 偽装と信頼できるプロキシ(IPで数えるときの前提)
- 運用:組織のセキュリティ基準(異常に気づける状態の作り方)