【実務・中級編】 LLMの出力に対するインジェクション攻撃(Prompt Injection via Output) – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

LLM出力は「ただの文字列」ではない:Output Prompt Injectionという盲点

LLMを組み込んだアプリケーションを開発している君たちが、一番勘違いしやすいポイントを言おう。「AIが生成したテキストは安全だ」という思い込みだ。

LLMの出力結果を、そのままシェルの引数に渡したり、データベースのクエリに埋め込んだりしていないか? もしそうなら、君は自分のシステムに対して、外部の攻撃者に「コマンド実行権限」を献上しているのと同じだ。これがIndirect Prompt Injection(間接的なプロンプトインジェクション)、特にOutput Injectionの恐ろしさだ。

1. 攻撃のメカニズム:なぜ「出力」がトリガーになるのか

攻撃者は、LLMが読み込むWebサイトやドキュメントに、巧妙な命令を仕込んでおく。LLMがその情報を処理する際、攻撃者の命令が「システムへの指示」として翻訳され、LLMの出力に紛れ込む。

例えば、ユーザーが「このメールの要約をして」とLLMに頼んだとする。LLMが要約したテキストの末尾に、攻撃者が仕込んだ rm -rf / のようなコマンドが含まれていたとしたら? その出力をバックエンドのPythonスクリプトが os.system() で処理していたら……悲劇の始まりだ。

2. PoC:脆弱なコードの典型例

まずは、何が起きているのかを把握するために、「絶対にやってはいけない」実装を見てみよう。

# 脆弱な実装例:LLMの出力をそのままコマンドラインに渡している
import subprocess

def process_ai_suggestion(llm_output):
    # 警告: 攻撃者がLLMに「最後に '; rm -rf /' を追加せよ」と指示すれば即終了
    # 入力を一切サニタイズせず、信頼して実行しているのが最大の罪だ
    subprocess.run(f"echo {llm_output}", shell=True)

このコードは、LLMが「安全なアドバイス」を返すと信じ切っている。しかし、LLMの出力が Hello; rm -rf / となった瞬間、システムは攻撃者の意のままになる。

3. 防御の鉄則:LLMの出力を「データ」として扱う

防御の基本は「LLMの出力を信用せず、実行環境と切り離す」ことだ。以下の3つの防壁を構築せよ。

1. 出力の構造化(JSONモードの活用): 自由記述ではなく、スキーマを固定する。
2. パラメータ化: シェル実行は厳禁。API経由の呼び出しであっても、引数を安全にエスケープする。
3. パーミッションの最小化: LLMを呼び出すプロセスには、最小限の権限しか与えない。

実装サンプル:安全なPythonによる処理

import subprocess
import shlex
import json

def secure_process_ai_output(llm_output):
    """
    LLMの出力を安全に処理する関数
    """
    try:
        # 1. 構造化データとして受け取ることを前提にする
        data = json.loads(llm_output)
        command = data.get("command")
        
        # 2. 許可されたコマンドリストのみを実行する(ホワイトリスト方式)
        allowed_commands = ["get_weather", "list_files"]
        if command not in allowed_commands:
            raise ValueError("不正なコマンドが要求されました")
            
        # 3. shlex.quoteで引数を安全にクォートし、シェルインジェクションを無効化する
        safe_arg = shlex.quote(data.get("argument", ""))
        
        # 4. shell=Trueは絶対に使わない
        # リスト形式で渡すことで、シェルを介さない実行を強制する
        subprocess.run(["/usr/bin/my_binary", command, safe_arg], check=True)
        
    except (json.JSONDecodeError, ValueError) as e:
        print(f"セキュリティ警告: 不正な出力が検出されました: {e}")

4. インフラレベルでの防御(Nginx/IAM)

コードレベルの対策に加え、万が一の侵害に備えてサンドボックス化しておく必要がある。

  • IAMの設定: LLMを呼び出すLambdaやコンテナには、S3の読み込み権限などは付与しても、ec2:TerminateInstances や rds:DropDatabase のような権限は絶対に与えないこと。
  • WAFでのフィルタリング: 外部からLLMに与えるプロンプト自体にもWAFで script タグや javascript: などの文字列を検知し、ブロックする設定を入れよ。
# Nginx設定例:怪しい文字列を含むリクエストをブロックする
location /api/ai-chat {
    # SQLインジェクションやクロスサイトスクリプティングの兆候をフィルタリング
    if ($arg_prompt ~* "(<script|javascript:|alert\(|drop table|--)") {
        return 403;
    }
    proxy_pass http://backend_cluster;
}

最後に:エンジニアとしての矜持

「AIが賢いから勝手にやってくれる」という楽観的な設計は、セキュリティの現場では怠慢でしかない。LLMはただの「確率的に次の単語を予測する関数」であり、そこに悪意や文脈理解の責任を負わせるべきではないんだ。

君たちが書くコードの一行一行が、システムの堅牢さを決める。LLMの出力を処理する際は、常に「これは攻撃者が仕掛けた罠かもしれない」という疑念を持って実装に臨んでほしい。それが、プロのエンジニアの仕事だ。

コメント

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