【実務・中級編】 Volatility 3を用いたコンテナメモリイメージのプロファイル作成 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

コンテナのメモリは「消える」のか? Volatility 3で追う、見えない侵入者の足跡

現場でインシデント対応をしていると、よくこんな相談を受ける。「コンテナが侵害された可能性があるが、Podを再起動したら証拠が消えてしまった。メモリフォレンジックなんてコンテナじゃ無理だよね?」と。

結論から言おう。無理ではない。だが、知識なしでは「ゴミ」しか手に入らない。

今日は、DockerやKubernetes環境でVolatility 3を使い、コンテナ特有のカーネル構造を解明する方法を伝授する。教科書的な「ツールの使い方」ではなく、攻撃者がなぜコンテナを狙うのか、そしてどうやって彼らの息の根を止めるかという、泥臭いDFIRの現場の話だ。

—

コンテナという「見えない檻」の盲点

攻撃者は、コンテナが「揮発性」であることを悪用する。メモリ上に悪意のあるバイナリをロードし、ディスクに一切書き込まずにバックドアを維持する。いわゆる「Fileless Malware」だ。

彼らは、コンテナの特権昇格脆弱性(例: runcの脆弱性など)を突き、ホストのカーネルを汚染する。Volatility 3は強力だが、コンテナは独自のカーネルシンボルテーブルを持たないことが多いため、デフォルトのプロファイルでは解析が止まってしまう。ここで必要になるのが、「中間シンボルテーブル(JSON形式)」の生成だ。

Volatility 3でコンテナ用シンボルテーブルを生成する

Volatility 3は、Volatility 2までの「プロファイル」概念を捨て、シンボルテーブルを必要とする。コンテナのカーネルイメージからこれを作成するには、以下の手順が必要だ。

1. 中間ファイルの抽出

まずは、実行中のホストから dwarf2json を使い、カーネルのデバッグ情報を抽出する。

# ホストのカーネルソースからDWARF情報を抽出し、JSONに変換する
# これがVolatility 3がメモリ構造を解釈するための「地図」になる
./dwarf2json linux --elf /usr/lib/debug/boot/vmlinux-$(uname -r) --system-map /boot/System.map-$(uname -r) > kernel_map.json

この kernel_map.json をVolatilityの intermediate ディレクトリに放り込むだけで、解析精度は劇的に変わる。

—

攻撃者の手口:なぜコンテナが狙われるのか

攻撃者は、コンテナ内のアプリが権限を逸脱した瞬間、メモリ上の ptrace を悪用して他のプロセスにインジェクションを試みる。特に、PHPやPythonで書かれたWebアプリの脆弱性(RCE)は格好の入り口だ。

以下は、典型的な攻撃者が行う「プロセスの隠蔽」を防ぐための、セキュアな実装コードだ。

【実装例】PHPによるコマンド実行の徹底ガード

多くのエンジニアは exec() を安易に使うが、これが命取りになる。必ず、実行可能なコマンドをホワイトリストで制限せよ。

<?php
/**
 * セキュアなコマンド実行ラッパー
 * 外部からの入力を直接コマンドに渡すのは自殺行為である
 */
function secure_execute($command, $args) {
    // 許可されたコマンドのみを定義
    $allowed_commands = [
        'convert' => '/usr/bin/convert',
        'zip'     => '/usr/bin/zip'
    ];

    if (!array_key_exists($command, $allowed_commands)) {
        throw new Exception("許可されていないコマンドです");
    }

    // 引数をシェルエスケープし、意図しないオプション挿入を防ぐ
    $safe_args = array_map('escapeshellarg', $args);
    $cmd = $allowed_commands[$command] . ' ' . implode(' ', $safe_args);

    // 実行結果を安全に取得
    exec($cmd, $output, $return_var);
    return $output;
}

—

守りを固めるためのインフラ構成(Kubernetes/Nginx)

メモリフォレンジックが最後の手札であるなら、最初の防壁は Seccomp や AppArmor だ。これらを使わないコンテナは、鍵のかかっていない玄関と同じだ。

Nginxのセキュリティヘッダー設定(nginx.conf)

情報の漏洩を防ぐため、HTTPレスポンスヘッダーを厳格に管理する。

server {
    # 攻撃者がバージョンを知ることを防ぐ
    server_tokens off;

    # メモリインジェクションやクロスサイトスクリプティングを抑制
    add_header X-Content-Type-Options "nosniff";
    add_header X-Frame-Options "DENY";
    add_header Content-Security-Policy "default-src 'self'; script-src 'self';";
}

KubernetesのPodセキュリティコンテキスト

「特権コンテナ」は絶対に許してはならない。

apiVersion: v1
kind: Pod
metadata:
  name: secure-app
spec:
  securityContext:
    runAsNonRoot: true # rootで動かす必要はどこにある?
    runAsUser: 1000
  containers:
  - name: app
    securityContext:
      allowPrivilegeEscalation: false # プロセスが特権を得ることを禁止
      readOnlyRootFilesystem: true   # ディスクへの書き込みを許さない(攻撃者の足場を奪う)

—

最後に:エンジニアが持つべき「疑いの目」

メモリフォレンジックは、単なるパズルのような作業ではない。「なぜこのプロセスがここにいるのか?」「なぜこのメモリ領域に実行権限(RWX)がついているのか?」という違和感を突き詰める作業だ。

コンテナのメモリイメージを適切にプロファイリングし、攻撃者が「存在しないはずの場所」に書き込んだバイナリを特定する。その技術力こそが、あなたのサービスを最後の砦として守り抜く。

「再起動すれば消えるから大丈夫」という甘い考えは今日で捨てよう。攻撃者は再起動の裏側で、あなたのコンテナを永続的な足場にしようとしているのだから。

何か不審な挙動があれば、まずは dwarf2json を回す準備から始めてみてほしい。現場のインシデント対応において、準備の差が勝敗を分ける。頑張れ。

コメント

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