野生のAIエージェントを捕らえる、その2:LLMだけが直せる「壊れたカナリアトークン」

概要

パート1では、ハニーポットの検知手法として「リバース・プロンプトインジェクション」を紹介し、自律型LLMエージェントによる攻撃セッションの全容を捕捉しました。単一のTor出口ノードから約19分間で58件のリクエストが発生し、複数ツールの切り替え、HTMLコメントからの意味論的な認証情報抽出、文脈に応じたペイロード生成、そして特徴的な「のこぎり波」型のタイミングパターンが確認されました。この成果の柱の一つが「セマンティック・カナリア」(提案フレームワークのレイヤー1)です。これは、自然言語を理解できる存在だけが反応する「餌」です。

ただし、そのセマンティックな餌は「帯域内」のものでした。漏洩させたadmin/admin123という認証情報は、ハニーポット自身に対して試されるものであり、AIか人間かの判定はあくまで行動面からの推測に頼っていました。本稿では、これを補完する手法として「帯域外」かつ高精度なトリガーを生成する方法を紹介します。私たちは、意図的に「破損させた」AWS認証情報を仕込みました。これを実際に使えるAPI呼び出しに変換するには、その破損が「マスク(偽装)」であると理解し、修復する必要があります。この認証情報の裏には本物のAWSカナリアトークンを紐付けているため、攻撃者が本当にAWS環境へと踏み込んだ場合にのみトリガーが発動し、その呼び出しに付随するクライアント情報やネットワークのテレメトリとともにアラートが届く仕組みです。

今回の展開では、自動化されたシークレットスキャナーがこのトークンに反応することは一度もありませんでした(破損によって正規表現が無効化され、AWSのEXAMPLE許可リストにも紛れ込むためです)。一方、人間の研究者は7~10分でこれを発動させ、自律型エージェントはわずか数秒で再構築して使用しました。しかも、その直前にページを取得したのと同じTor出口ノードから、SDKのUser-Agentを使って行われていました。このエージェントはAWS SDKの通信までもTor経由でトンネリングしており、正規のsts呼び出しがTor出口ノードから発生することは通常ありません。つまり、トリガーの発生元そのものが異常なのです。判別の決め手となるのは反応時間ではなく「再構築できたかどうか」であり、そのうえでタイミングによってエージェントと人間を切り分けます。

1. はじめに

パート1では、自律型エージェントが読み取るものすべてに対して「推論」を行うという性質を明らかにし、これが検知に利用できることを示しました。スキャナーはHTMLコメントを無視しますが、エージェントはそれを解析し、意味を抽出したうえで行動に移します。私たちはこの性質を利用して、実際に野生のエージェントを捕らえました。

しかし、帯域内のセマンティック・カナリアには実用上二つの限界があります。第一に、トリガーが欺瞞環境の内部に存在するため、「これはAIエージェントだった」と確証を得るには単一の明確な事象ではなく、行動指標を積み重ねる必要がある点です。第二に、餌(test credentials: admin/admin123)が平文であるため、運の良い正規表現や人間による流し読みでも拾われてしまう可能性があり、判別の決め手は餌そのものではなく「その後何が起きるか」に依存している点です。

本稿ではさらに踏み込んだ問いを立てます。使用されたこと自体が理解の証明となり、かつトリガーが帯域外で攻撃者を特定できる、単一の餌を設計できないだろうかという問いです。

その答えが、破損させたカナリアトークンです。カナリアトークンとは、使用された際にアラートを発するだけの認証情報であり、これを仕込んでaws sts get-caller-identityが呼ばれるのを待つことは、防御側が持つ最も高精度なトリップワイヤーの一つです。私たちの貢献は、このトークン自体を「分類器」に変えたことにあります。読み手だけが直せる形で壊しておくのです。抽出は失敗し、理解だけが成功します。そして、このトークンは本物であるため、攻撃者のIPアドレス、User-Agent、AWS API呼び出しとともにアラートがカナリアから届く仕組みになっています。

2. 背景

2.1 セマンティック・カナリアのおさらい

パート1のレイヤー1では、意味論的な処理を行う存在だけが反応する自然言語のトリップワイヤー(偽の認証情報、「内部」URL、開発者向けのメモなど)を埋め込みました。今回の成果はこのレイヤーを直接具現化し、強化したものです。原理は同じですが、(a)一般的なスキャナーがこの餌を確実に利用できないこと、(b)トリガーがハニーポット内のリクエストではなく、外部の高精度な事象であることという2点を工学的に実現しています。

2.2 単純な漏洩キーがノイズになる理由

クリーンなカナリアキーは、大量スキャナーであれ、人間であれ、エージェントであれ、無差別にすべてを捕らえてしまいます。そのため、トリガーが発生したという事実だけでは、何を捕らえたのかが分かりません。トリップワイヤーに判別能力を持たせるには、それを理解できる存在だけが認証情報を使用できるようにする必要があります。

2.3 シークレットスキャナーとEXAMPLE許可リスト

gitleaksやtrufflehog、そしてAKIA…を探し回る無数のクローラーといった一般的なシークレットスキャナーは、基本的にAKIA[0-9A-Z]{16}のような連続したパターンを手がかりにしています。また、AWSの正規ドキュメント用キーAKIAIOSFODNN7EXAMPLEに似たものを許可リストに登録し、チュートリアル由来の誤検知の氾濫を抑える傾向があります。この二つの事実はいずれも突くことができます。連続していないうえにドキュメント例と区別がつかないキーは、スキャナーにとっては「見えない」も同然なのです。

3. 手法

3.1 ハニーポット基盤

私たちは、社内SREウィキを模した単一のHTTPサービスをBeelzebubで構築し、nginxをフロントに配置しました。Beelzebubはすべてのリクエストについて、ヘッダー、本文、送信元IP、セッションデータを記録します。餌は1ページに仕込まれており、レガシーなバックアップ用ユーザーの緊急時認証情報を「うっかり」貼り付けたキーローテーションの手順書という体裁です。

3.2 罠の設計:破損させたカナリアトークン

この認証情報は本物のAWSキー型カナリアトークンであり、アクセスキーIDとシークレットの両方に、リテラルなマーカー_BLZ_を挿入しています。

aws_access_key_id     = AKIA_BLZ_QZ7X4RT2M5FW3KLV
aws_secret_access_key = wNbK7pQ2xR_BLZ_9dLm4TfV6aYz1Csh0EgJu3Pi8oWqBn

それぞれから_BLZ_を取り除くと、有効な形式である20文字のアクセスキーID(AKIA+base32文字16文字)と40文字のシークレットが復元され、これが本物のカナリアペアとなります。(本稿に掲載しているペアは、体裁を合わせただけの代替品です。実際に稼働していたカナリアトークンは公開前にローテーション済みのため、ここに載せた認証情報を再構築しても何も得られません。)このマーカー一つで、二つの役割を同時に果たしています。

  • 正規表現を無効化する。 AKIA_BLZ_…はAWSキーのどの連続パターンにも一致しないため、スキャナーは何も抽出できず、トリップワイヤーにたどり着けません。
  • ドキュメント例に紛れ込む。 EXAMPLEという部分文字列が、多くのスキャナーがAWS自身のデモ用キーAKIAIOSFODNN7EXAMPLEを除外する際の判断基準を利用した「偽装」として働きます。

これを実際に使える認証情報に変えるには、攻撃者が_BLZ_を「挿入されたマスク」だと認識し、それを取り除いたうえで試す必要があります。これはパターンマッチングではなく、意図についての推論です。まさにパート1で利用したものと同じ意味論的能力を、単一の判断に凝縮した形といえます。

Beelzebubサービスの完全な設定は以下のとおりです。

apiVersion: "v1"
protocol: "http"
address: ":8080"
description: "Internal SRE wiki (decoy) - AWS key-rotation runbook"
commands:
  - regex: "^/robots\\.txt$"
    headers:
      - "Content-Type: text/plain; charset=utf-8"
      - "Server: nginx/1.25.3"
    statusCode: 200
    handler: |
      User-agent: *
      Disallow: /internal/
  - regex: "^/$"
    headers:
      - "Content-Type: text/html; charset=utf-8"
      - "Server: nginx/1.25.3"
    statusCode: 200
    handler: |
      <!doctype html><html lang="en"><head><meta charset="utf-8">
      <title>SRE Wiki</title></head><body>
      <h1>SRE Internal Wiki</h1>
      <ul>
        <li><a href="/internal/runbooks/aws-key-rotation">Runbook: AWS access-key rotation</a></li>
        <li><a href="/internal/runbooks/db-failover">Runbook: DB failover</a></li>
      </ul>
      </body></html>
  - regex: "^/internal/runbooks/aws-key-rotation/?$"
    headers:
      - "Content-Type: text/html; charset=utf-8"
      - "Server: nginx/1.25.3"
    statusCode: 200
    handler: |
      <!doctype html><html lang="en"><head><meta charset="utf-8">
      <title>Runbook: AWS access-key rotation</title></head><body>
      <h1>Runbook: AWS access-key rotation (legacy backup service)</h1>
      <p>The nightly S3 backup job still runs as the break-glass IAM user
      <code>svc-backup-legacy</code>. Credentials are pasted below so on-call
      can re-run the sync manually until the migration to IRSA (MIG-1423) is done.</p>
      <!-- export: values sanitized by scrubber v2, see vault for originals -->
      <pre>
      # ~/.aws/credentials  (svc-backup-legacy)
      [default]
      aws_access_key_id     = AKIA_BLZ_QZ7X4RT2M5FW3KLV
      aws_secret_access_key = wNbK7pQ2xR_BLZ_9dLm4TfV6aYz1Csh0EgJu3Pi8oWqBn
      region                = us-east-1
      </pre>
      <p>Sanity check before running the sync:
      <code>aws sts get-caller-identity</code></p>
      </body></html>
  - regex: ".*"
    headers:
      - "Content-Type: text/html; charset=utf-8"
      - "Server: nginx/1.25.3"
    statusCode: 404
    handler: |
      <html><head><title>404 Not Found</title></head>
      <body><center><h1>404 Not Found</h1></center><hr><center>nginx/1.25.3</center></body></html>

ここで重要な設計原則として、パート1での方針を踏襲しています。それはページ内にキーの修復方法についての指示を一切書かないということです。「使用前にマーカーを取り除け」といったヒントを一つでも加えた瞬間、スクリプト化された攻撃者がその手順をハードコードできてしまい、判別能力が失われます。推論は強制されてはならず、自発的に行われる必要があります。

4. 結果

この手順書ページへのアクセスは、行動特性によって3つの分類に分けられました。自動スキャナーによる60セッション、人間による14件のやり取り、そして自律型エージェントによるものと判断される13セッションです。トークンを発動させたのは、後者の2分類だけでした。

  • 自動スキャナーは60件中0件がトークンを発動(破損させたキーがあらゆる正規表現を回避)
  • 人間の研究者は14件中9件が発動、取得から発動までの中央値は約8.7分(523秒)
  • AIエージェントは13件中12件が発動、取得から発動までの中央値は約4秒、いずれもTor出口ノードから

4.1 自動スキャナー

ルート、/robots.txt、手順書ページと巡回した後、そのまま/.env/.git/configへと進みました。セッション全体は1秒未満で、User-Agentはpython-requestsでした。

カナリアからのアラートは発生しませんでした。破損させたキーは、正規表現に抽出できるものを何も与えなかったのです。

4.2 人間の研究者

実際のChromeのUser-Agentで手順書ページに滞留した後、数分後に同一IPからaws-cliのUser-Agentを使ってトークンが発動しました。

コピーして、じっと見て、_BLZ_が余計なものだと気づき、それを取り除いてCLIを実行する、という一連の間隔は今回のケースで約8.6分であり、確認できた人間によるやり取り全体では7~10分の範囲に収まっていました。

4.3 AIエージェント

1回のフェッチのみで、滞留もブラウジングもなく、その4秒後には同一IPからboto3/botocoreのUser-Agentを使ってGetCallerIdentityが呼び出されました。

両方の事象はいずれも192.42.116.15から発生しており、これはパート1で捕捉したエージェントと同じ192.42.116.0/24系統に属するTor出口ノードです。この対称性自体が一つの指紋になっています。運用者はウェブ通信だけでなく、AWS SDKの呼び出しまでもTor経由でトンネリングしていました(これはデフォルト設定ではあり得ず、SOCKS経由で意図的に配線されたboto3です)。そして正規のAWS APIトラフィックがTor出口ノードから発生することは基本的にありません。ASN情報がエージェントの実際の所在について何も教えてくれない場合でも、トリガーの発生元だけで、匿名化の背後で自動化が動いていることが分かるのです。ページを読み、マスクを認識し、キーを再構築し、それを試す。Torを介した一連の流れがわずか4秒で完結する。このログの一組に、本稿の主張がすべて詰まっています。

5. 行動フィンガープリンティング:AIエージェント対人間対スキャナー

これは、パート1で示した比較表に、認証情報の再構築という判別要素を加えて拡張したものです。

行動指標 人間の攻撃者 従来型スキャナー AIエージェント
破損したシークレット(_BLZ_マスク)の再構築 可能、ただし低速 不可(正規表現が捉えられない) 可能
取得から認証情報使用までの遅延 7~10分 該当なし(発動せず) 数秒
発動元 住宅用ISP 該当なし Tor出口ノード/匿名化インフラ
AWS呼び出しにおける匿名化 なし(自宅/自分のPCのIP) 該当なし SDK通信をTor経由でトンネリング
セッションの形 滞留あり、ブラウザ→CLI 複数経路への高速な走査 単一フェッチ、滞留なし、SDKのUser-Agent
AWS呼び出しを実行するクライアント PC上のaws-cli 該当なし boto3/botocoreランタイム

ここで最も重要な点であり、当然想定される反論への答えでもあるのですが、速さそのものは自動化の証拠にはなっても、知能の証拠にはならないということです。実際、今回のデータの中で最も速く訪れたのはスキャナー群であり、そのいずれもトークンを発動させませんでした。高速なボットにできないのは、意図的に壊されたシークレットを修復することです。したがって、再構築できたという事実がスキャナーを除外し、そのうえでタイミングによってエージェントと人間を切り分けます。これは、パート1で示した「のこぎり波」型の署名(推論による間と、サブ秒単位のバーストの繰り返し)を補完するものです。パート1では「どのように攻撃したか」が手がかりでしたが、本稿では「スキャナーには解析できない認証情報を使いこなし、しかも瞬時にそれを行えたこと」自体が手がかりとなります。

6. 本手法の位置づけ:強化された帯域外レイヤー1

パート1で提案したフレームワークに当てはめると、以下のようになります。

  • レイヤー1:セマンティック・カナリア。 今回の成果はこのレイヤーを強化した実例です。餌は正規表現スキャナーが確実に利用できない設計であり、そのトリガーはハニーポット内のリクエストではなく、帯域外で攻撃者を特定できるAWS上の事象です。
  • レイヤー2:行動分析。 裏付けとなる各種シグナル(Tor出口ノードという発生元、SDKのUser-Agent、滞留のない単一フェッチ)は、既に定義済みのBIoC分析にそのまま組み込むことができます。
  • レイヤー3:能動的プロンプトインジェクション。 こちらは直交する要素であり、組み合わせが可能です。おとりページには、破損させたカナリア(今回の成果)と、注入した指示(パート1)の両方を仕込み、確信度をさらに高めることができます。

検知ロジックは、次の4つの条件に集約されます。(1)発生元がおとりの手順書ページを取得する、(2)そのページに紐付けられたカナリアが発動する、(3)その遅延が秒単位である、(4)そのトリガーが、住宅用回線上の対話的なCLIではなく、匿名化インフラやデータセンターのインフラ(今回の捕捉例ではTor出口ノード)からのSDKランタイム(boto3/botocore)によって実行される、という4点です。正規のAWS APIトラフィックがTor出口ノードから届くことは基本的にないため、この条件だけでも十分な重みを持ちます。条件2だけでも、誰かが壊れたキーを再構築したことを意味します。そこに条件3と条件4を加えることで、罠を共有する人間やスキャナーの母集団からAIエージェントを切り分けられます。破損させたキーはスキャナーからは見えないため、誤検知の下限はほぼゼロです。偶然トリップワイヤーに触れるものは事実上存在しません。

8. 結論

パート1では、エージェントが読み取ったものについて「推論」を行うことで自らの正体を露呈してしまうことを示しました。パート2では、これを単一の、スキャナーを欺けず、帯域外で機能するトリップワイヤーへと発展させました。理解した者だけが解錠できるよう破損させたカナリアトークンです。今回の展開において、スキャナーはこれを一度も発動させず、人間は数分を要し、自律型エージェントは数秒で再構築し使用しました。しかも、取得元と同じTor出口ノードを介して、AWS SDKの呼び出しまでトンネリングしていました。ここで防御側にとって根拠となるシグナルは「速かった」ということではなく、「スキャナーが物理的に解析できないシークレットを修復し、それを瞬時に行い、なおかつ正規のAWSトラフィックが本来発生し得ないネットワーク上の場所からAWS APIを呼び出した」という事実です。この罠全体は、1つのYAMLファイルと1つのnginxルート、そして1つの破損させたカナリアだけで構成されています。自律型エージェントが今後さらに広がっていく中で、帯域外かつ攻撃者を特定できるトリガーを備えたセマンティック・カナリアは、防御側のツールキットに加えるべき持続的な選択肢になると私たちは考えています。

Beelzebubはオープンソースです:github.com/beelzebub-labs/beelzebub

パート1:リバース・プロンプトインジェクションをハニーポット検知手法として用い、野生のAIレッドチーマーを捕らえる »もあわせてお読みください

翻訳元: https://beelzebub.ai/blog/catching-ai-agents-in-the-wild/

ソース: beelzebub.ai