メインコンテンツへスキップ

LLMで偽装Webページを自動生成するハニーポット

·1 分

LLMを使ってリアルなWebハニーポットを生成・運用するためのツール tamamo を実装したので、背景と設計上の判断について解説します。

モチベーション
#

ハニーポットは攻撃者を捕捉する様々な目的で利用されます。多くは攻撃者の挙動を研究するために使われますが、攻撃者の検知にも有効です。ただし public internet は常に攻撃にさらされているため、攻撃の検知という用途には不向きです。一方で、内部ネットワークにおけるラテラルムーブメントの検出には非常に有用です。内部ネットワークでは原則として攻撃的なアクセスは発生しないため、ハニーポットへのアクセス自体が異常の兆候になります。

しかし従来のWebハニーポットには、静的すぎるという問題があります。固定的なログイン画面やダッシュボードは、攻撃者にとって見破りやすいものが多く、OSSとして公開されているものであれば照合も容易です。これまではそれっぽいログイン画面を個別に用意するのも手間がかかり、現実的ではありませんでした。さらに、任意のクレデンシャルでログインが成功してしまう、レスポンスのサーバーシグネチャが不自然、ページの作りが雑すぎるなど、実際の管理画面とは明らかに異なる挙動も怪しまれる原因になります。

設計
#

tamamo は LLM(OpenAI、Claude、Gemini に対応)を使って、ログイン画面、ダッシュボード、APIエンドポイント、サーバーシグネチャを一括生成します。生成結果は scenario.json(メタデータとサーバーシグネチャ)と routes.json(ルーティング定義)、HTMLファイル群を含むZIPファイルとしてパッケージングされます。

scenario.json       # メタデータ、サーバーシグネチャ、ヘッダ
routes.json         # パス、メソッド、レスポンス定義
pages/
  login.html        # ログイン画面
  dashboard.html    # ダッシュボード(永続ローディング)
  logo.svg          # 生成されたアセット

サイトの種類(--site-type)、ビジュアルスタイル(--site-style)、雰囲気(--site-taste)、表示言語(--site-lang)などをパラメータとして指定できます。

欺瞞手法
#

ページ生成の多様化
#

tamamo の欺瞞の土台になっているのは、LLMによるページ生成の多様性です。LLMは19種類以上のレイアウトパターンからログインページを生成します。centered-card、split-screen、terminal-cli、dialog-modal など多様なレイアウトに加え、フォームのバリエーションも豊富です(username/email、MFA入力欄、SSO/SAMLボタンなど)。同じシナリオタイプでも生成するたびに見た目が変わるため、既知のハニーポットとの照合による検出が困難になります。

以下は実際に tamamo が生成したログインページの例です。いずれもデフォルト設定で生成したもので、サイトの種類やスタイルはLLMがランダムに決定しています。

TARM System — ミリタリー風のダイアログモーダル
TerraCorp Intranet — コーポレートゲートウェイ
NIGHT-WITCH — ターミナルCLI風
GlobalPOS — POS端末管理画面

サイトの種類(--site-type)、ビジュアルスタイル(--site-style)、雰囲気(--site-taste)、表示言語(--site-lang)を明示的に指定すれば、デプロイ先の環境に合わせたシナリオも生成できます。

認証シミュレーション
#

ハニーポットで最も怪しまれやすいのが認証周りの挙動です。多くのハニーポットは初回のログイン試行で即座に成功レスポンスを返す実装もありますが、実際のサービスでは攻撃者が一発でパスワードを正解する可能性は低く、何度か試すことが予想されます。tamamo はソースIPごとに認証試行を追跡し、リアルなログイン体験をシミュレーションします。具体的には以下の3つのパラメータで制御します。

  • min_failures: 最低何種類のクレデンシャルが失敗するまで成功させないか
  • success_probability: min_failures 到達後に新しいクレデンシャルが成功する確率(0.0〜1.0)
  • credential_fields: どのリクエストボディのフィールドをクレデンシャルとして扱うかを指定(例: ["username", "password"]

クレデンシャルのユニーク性はSHA-256ハッシュで判定しており、同じクレデンシャルは常に同じ結果を返します。credential_fields を指定することで、CSRFトークンなど認証に無関係なフィールドは判定から除外されます。また、成功判定もハッシュから決定論的に導出されるため、リプレイしても結果が変わりません。

{
  "path": "/api/auth/login",
  "method": "POST",
  "auth": {
    "min_failures": 3,
    "success_probability": 0.2,
    "failure_status_code": 401,
    "failure_body": "{\"success\": false, \"error\": \"Invalid credentials\"}",
    "failure_headers": {"Content-Type": "application/json"},
    "credential_fields": ["username", "password"]
  }
}

サーバーシグネチャの偽装
#

攻撃者はレスポンスヘッダからサーバーの種類を特定しようとします。tamamo では scenario.jsonserver_signature フィールドでHTTPレスポンスの Server ヘッダを偽装できます(例: nginx/1.24.0Apache/2.4.41)。headers フィールドで X-Powered-ByX-Frame-Options なども付与可能です。これらはすべてのレスポンスに自動適用されるため、攻撃者のフィンガープリンティングに対して一貫した偽装を提供します。

Hangルート
#

hang: true を設定したルートはレスポンスを返さず、コネクションを保持し続けます。これをダッシュボードの「Loading…」画面として使うと、ログイン成功後に攻撃者を引き留めることができます。攻撃者はシステムが遅いだけだと認識し、待ち続けます。コネクション保持中もイベントは記録されるため、観測には影響しません。

TLS証明書のバックデート
#

--tls フラグを指定すると自己署名証明書を自動生成してHTTPSで配信します。ここで重要なのは、証明書の NotBefore(有効期間の開始日)を3〜12ヶ月前にランダムにバックデートしている点です。生成直後の証明書は NotBefore が現在時刻になるため、「このサーバーはたった今立ち上げた」ということが証明書から読み取れてしまいます。バックデートすることで、以前から稼働しているサーバーのように見せかけます。外部の証明書を --tls-cert / --tls-key で指定することも可能です。

観測手法
#

先述の通り、tamamo を内部ネットワークに配置した場合、そのホストにアクセスしてきた時点でかなりグレーな状況です。それに加えてログイン試行のログをイベントとして記録すれば、かなりの確度で攻撃だと検証できます。

攻撃者のすべてのインタラクションは構造化イベントとしてキャプチャされます。HTTPリクエストのメソッド、パス、ヘッダ、ボディ、ソースIP、対応するシナリオ名がJSON形式で出力されます。

{
  "timestamp": "2026-03-20T12:34:56Z",
  "node_id": "honeypot-01",
  "event_type": "http_request",
  "source_ip": "203.0.113.1",
  "method": "POST",
  "path": "/api/auth/login",
  "body": {"username": "admin", "password": "P@ssw0rd"},
  "scenario": "acme-corp-admin"
}

ログ出力に加えて、Webhook(HMAC-SHA256署名付き)やGoogle Cloud Pub/Subへの送信にも対応しています。既存のセキュリティ監視パイプラインへの統合を想定した設計です。

再現性:シナリオの保存と検証
#

LLMによる生成で懸念されるのは再現性です。毎回ランダムに生成して、壊れたHTMLや不整合なルーティングが出力される「動けばラッキー」なPoCでは実運用に耐えません。

tamamo はこの問題を2つの仕組みで解決しています。

1つ目はZIPによるシナリオ保存です。生成結果をZIPファイルとして保存し、同じシナリオを何度でもデプロイできます。シナリオのバージョン管理や共有も容易です。

2つ目は tamamo validate による検証です。保存されたシナリオに対してバリデーションを実行し、ルーティングの不整合やHTMLの欠損などを事前にチェックできます。生成時にも --max-retries でバリデーション失敗時の自動リトライが設定可能です。

さいごに
#

tamamo のコードは GitHub で公開しています。どなたかの参考になれば幸いです。