コンテナのメモリは「消える」のか? 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 を回す準備から始めてみてほしい。現場のインシデント対応において、準備の差が勝敗を分ける。頑張れ。
コメント