【実務・中級編】 LLMのトークン消費量監視によるDoS攻撃検知 – ガバナンス・リスク管理 & 生成AIセキュリティ防御ガイド

エンジニア諸君、現場の最前線は今日も戦場だ。

生成AIの導入が「とりあえずAPIを叩いて動くものを作る」段階から「ビジネスの核心として可用性を維持する」段階へと移行する中、セキュリティ担当として一つだけ断言しておく。「トークン消費量=コスト」と考えているうちは、君たちのサービスはいつか必ず死ぬ。

今の攻撃者は、君たちのLLMエンドポイントを「安価な計算リソース」や「サービスを麻痺させるための踏み台」として見ている。特に、入力プロンプトの設計次第で計算量を跳ね上げられる「LLM DoS(Denial of Service)」は、脆弱性の教科書には載っていないが、現場で最も恐ろしいインシデントの一つだ。

今日は、トークン消費量を武器にサービスを焼き払いに来る攻撃者から、君たちのプロダクトを守り抜くための「実戦的防衛術」を伝授する。

1. なぜ「トークン消費量」が攻撃の標的になるのか

LLMにおけるDoS攻撃は、単純なトラフィック増大ではない。攻撃者は「極めて長い入力文」や「再帰的な推論を誘発するプロンプト」を投げることで、以下のリソースを枯渇させる。

  • 推論時間の増大: 応答が返らない間に同時接続数上限に達し、正規ユーザーがタイムアウトする。
  • コストの爆発: 従量課金API(OpenAIやClaudeなど)の予算を数分で使い切る。
  • バックエンドの負荷: トークン生成に伴うデータベースやログ基盤への書き込み負荷を増大させる。

これに対抗するには、WAFでのリクエスト数制限(Rate Limit)だけでは不十分だ。「リクエストの中身(トークン数)」を評価軸にした動的な制御が必要になる。

2. Pythonでの防衛実装:トークンベースのゲートキーパー

API層の直前で「見積もり(Estimated Cost)」を行い、異常値を弾くゲートキーパーを実装しよう。tiktokenライブラリを用いて、リクエストを受け取った瞬間に消費トークンを算出する手法だ。

import tiktoken
from fastapi import HTTPException, Request, status

# トークン計算用のエンコーダー(モデルに合わせて選択)
encoder = tiktoken.get_encoding("cl100k_base")

def check_token_limit(prompt: str, max_tokens: int = 2000):
    """
    リクエストに含まれるトークン数を計算し、閾値を超えたら拒否する
    """
    token_count = len(encoder.encode(prompt))
    
    # 異常に長いプロンプトは攻撃とみなす
    if token_count > max_tokens:
        # ここでログを出力し、SIEM(セキュリティ情報イベント管理)へ通知を送る
        print(f"SECURITY ALERT: Potential DoS attempt. Tokens: {token_count}")
        raise HTTPException(
            status_code=status.HTTP_429_TOO_MANY_REQUESTS,
            detail="プロンプトが長すぎます。リクエストを制限しました。"
        )
    return token_count

# FastAPIでの使用例
@app.post("/chat")
async def chat_endpoint(request_body: dict):
    prompt = request_body.get("prompt", "")
    # ロジックの実行前に必ず検証
    check_token_limit(prompt)
    # ... 以下、LLM呼び出し処理

この実装の肝は、「LLMを叩く前に」計算を完結させることだ。このチェックをパスしない限り、高額なLLM APIを叩くことも、GPUリソースを消費することもない。

3. Nginx と Lua によるエッジでのブロック

インフラの入口で防ぎたいなら、Nginx + Lua(OpenResty)の出番だ。WAFで「特定のパスへのリクエストサイズ」を制限するのも有効だが、よりインテリジェントにやるなら、Luaスクリプトでリクエストボディを解析し、Content-Length ではなく実際のトークン数に近い文字数で制限をかける。

# nginx.conf の設定例
location /api/v1/generate {
    # ボディサイズを制限(攻撃によるメモリ枯渇防止)
    client_max_body_size 50k; 

    access_by_lua_block {
        local body = ngx.req.get_body_data()
        -- 文字数が極端に多い場合は即座に遮断
        if body and #body > 40000 then
            ngx.exit(ngx.HTTP_REQUEST_ENTITY_TOO_LARGE)
        end
    }
    proxy_pass http://backend_cluster;
}

4. セキュリティチーフからの「泥臭い」アドバイス

技術的な実装以上に、以下の運用ルールを徹底してほしい。

1. 異常値の可視化:
ただ遮断するだけではダメだ。Prometheus や Datadog で「リクエストごとのトークン消費量」をヒストグラム化し、急激なスパイクを検知できるアラートを構築すること。普段の利用者のトークン消費中央値を知らなければ、何が「異常」かなんて判断できない。
2. IAMの最小権限の原則:
APIキーをアプリケーション全体で共有していないか?環境変数で管理し、最悪流出しても特定の予算上限しか使えないよう、プラットフォーム側の「予算管理機能(Budget Alerts)」と「APIキー毎の制限」を必ず設定すること。
3. 攻撃者プロファイリング:
DoS攻撃を仕掛けてくるIPアドレスは、多くの場合、短期間に何度も失敗する。WAF側で「一定時間内に複数回 429(Too Many Requests)を返したIP」を自動的にブラックリストへ追加するよう、セキュリティ自動化(SOAR的な運用)を組んでおくべきだ。

セキュリティとは、完璧な壁を作ることではない。「攻撃者にコストを支払わせ、自分たちのリソースを死守し、攻撃の兆候をいち早く見つけること」だ。

諸君、コードを書くときは常に「これが悪意あるユーザーにどう悪用されるか」という視点を忘れないように。それができるエンジニアこそが、次世代のリードエンジニアだ。健闘を祈る。

コメント

タイトルとURLをコピーしました