コンテナの「死」を看取るな:現場で戦うためのメモリフォレンジック術
コンテナ環境でのインシデントレスポンス(IR)は、いわば「時限爆弾を抱えながら外科手術をする」ようなものだ。従来の物理サーバーやVMであれば、メモリダンプを採取する時間は十分にある。しかし、コンテナにおいて「調査のためにコンテナを停止します」などと言おうものなら、攻撃者が仕掛けたLiveness Probeや自動スケーリングのトリガーにより、次の瞬間にはコンテナが消滅し、証拠もろともフォレンジックの機会が霧散する。
今回は、我々が現場で直面する「コンテナの消失」という悪夢を回避し、メモリの整合性を保ちつつ「生きた証拠」を確保するための実践的なアプローチを解説する。
—
攻撃者が狙う「フォレンジックの盲点」
攻撃者は、侵害後にメモリ上で難読化されたペイロードを展開し、ファイルシステムには痕跡を残さない「ファイルレス攻撃」を好む。彼らは自身のプロセスを監視し、コンテナの再起動やリソース枯渇を検知すると、証拠を焼き切るスクリプトを仕込んでいることさえある。
ここで重要なのは、「コンテナを止めるのではなく、静止させる」という考え方だ。コンテナランタイムを一時停止(Pause)させ、外部からメモリをダンプする手法が、最もインテグリティ(整合性)を維持できる。
—
現場で使える:コンテナ静止とダンプの自動化
DockerやKubernetes環境で、コンテナを殺さずにメモリを吸い出すには、docker pause を活用し、ホスト側から gcore や LiME を叩くのが定石だ。以下に、インシデント発生時に即座に叩くべき、Pythonによる自動取得スクリプトのひな形を示す。
コンテナメモリ抽出用 Python スクリプト
このスクリプトは、対象コンテナを凍結(Pause)し、メモリダンプを取得後、即座に解凍(Unpause)する。
import subprocess
import time
def dump_container_memory(container_id, output_path):
try:
# 1. コンテナを凍結させ、実行状態を完全に固定する
print(f"[*] コンテナ {container_id} を凍結中...")
subprocess.run(["docker", "pause", container_id], check=True)
# 2. プロセスID(PID)を取得
pid = subprocess.check_output(
["docker", "inspect", "--format", "{{.State.Pid}}", container_id]
).decode().strip()
# 3. gcoreを使用してメモリダンプを取得(対象プロセスの全メモリをダンプ)
print(f"[*] PID: {pid} のメモリをダンプ中...")
subprocess.run(["gcore", "-o", output_path, pid], check=True)
print(f"[+] ダンプ完了: {output_path}.{pid}")
except subprocess.CalledProcessError as e:
print(f"[!] エラー発生: {e}")
finally:
# 4. 調査後は必ず解除(これを忘れるとサービスダウンに直結する)
subprocess.run(["docker", "unpause", container_id])
print("[*] コンテナの凍結を解除しました。")
# 実行例
# dump_container_memory("target_container_name", "/tmp/forensics/dump")
—
コンテナを「守る」ためのインフラ設定:ヘルスチェックの罠
フォレンジック中に最も恐ろしいのは、取得作業中にKubernetesの livenessProbe が「応答なし」と判断してコンテナを強制再起動(Kill)してしまうことだ。これを防ぐためには、フォレンジック専用の実行権限をIAMで制限しつつ、一時的にプローブを無効化する手順を準備しておく必要がある。
K8sにおける「証拠保全モード」の設定例
緊急時、対象コンテナのPod定義から一時的に livenessProbe を外す、あるいは initialDelaySeconds を極端に長くするパッチを当てるのが現場の知恵だ。
# パッチ適用用のJSONサンプル(kubectl patchで使用)
spec:
containers:
- name: app-container
livenessProbe:
# 調査中、一時的にタイムアウトを延長してKillを防ぐ
failureThreshold: 10
periodSeconds: 60
initialDelaySeconds: 300
—
エンジニアへの教訓:ログだけでは真実は見えない
最後に、心に留めておいてほしいことがある。ログは「攻撃者があなたに見せたいもの」であり、メモリは「攻撃者が実際に実行したすべて」だ。
1. タイムスタンプの整合性: メモリダンプ取得時は、ホストの時計とNTPのズレを確認すること。
2. サイドカーの活用: 本番環境では、フォレンジック専用のサイドカーコンテナをデプロイできるよう、Dockerfileに gdb や gcore を含める計画を立てておけ(ただし、本番イメージに含めるべきか否かはセキュリティポリシーと相談だ)。
3. 自動化の罠: 自動取得スクリプトは、必ずテスト環境で「コンテナが確実に再開するか」を検証してから運用に回すこと。
インシデントレスポンスは、準備が9割。今、君たちの目の前にあるコンテナが、次の瞬間に「空っぽの器」にならないよう、このコードを武器として備えておいてほしい。現場からは以上だ。次回の講義では、メモリダンプ内の難読化されたシェルコードをどうやって特定するか、その手法を紐解くとしよう。
コメント