メモリに刻まれた「悪意の足跡」を暴く:シェルセッション復元と防御の最前線
現場でインシデント対応をしていると、よく「ログを消したから大丈夫だ」と高を括っている侵入者に出会う。だが、彼らは致命的な見落としをしている。OSが稼働している限り、メモリ(RAM)は嘘をつかないという事実だ。
特に攻撃者がシェルを奪取した際、historyコマンドを無効化したり、unset HISTFILEで履歴保存を回避したりするのは定石だ。しかし、彼らが叩いたコマンドの引数や、メモリ上に展開されたスクリプトの断片は、揮発性メモリの中にゾンビのように生き残っている。今回は、その「消されたはずの痕跡」をどう追い、どう防ぐかという話をしよう。
—
1. なぜメモリフォレンジックが「最後の砦」なのか
攻撃者がWebアプリの脆弱性(RCE等)を突き、Webシェルを設置してコマンドを実行する際、そのプロセスは必ずメモリ上のバッファを経由する。
- bash/zshの挙動: セッションが終了するまで、履歴はメモリ上のリングバッファに保持される。
- PowerShellの挙動:
ScriptBlockLoggingが有効でない場合でも、メモリ上には実行されたコマンドの引数やデコード後の難読化スクリプトが平文で残る。
我々フォレンジック調査官は、LiMEやVolatilityといったツールを使い、ダンプしたメモリからbashプロセスのメモリ空間を特定し、そこから過去数時間分のコマンド履歴を「発掘」する。この作業は、泥沼の捜索だが、攻撃者の「生の息遣い」が聞こえる瞬間でもある。
—
2. 攻撃者が好む「足跡を消す」手口
攻撃者は、侵害した端末で以下のようなコマンドを真っ先に実行する。
# 履歴を書き出さないようにする典型的な手口
export HISTFILE=/dev/null
unset HISTFILE
history -c
これをやられると、ディスク上の .bash_history は更新されない。しかし、この瞬間も攻撃者はメモリ上でコマンドを打ち続けている。ここを狙うのがプロの調査だ。
—
3. 防御の要:ログの「外部強制転送」と「実行制限」
メモリフォレンジックで証拠を掴むのは調査の最終手段だ。理想は、攻撃者がメモリに痕跡を残した瞬間に、その内容を外部の堅牢なログサーバへ転送し、改ざん不能にすることにある。
【対策1】Linuxでのコマンド実行の完全ログ化(Auditd)
bash_history はユーザが操作できるが、カーネルレベルで動作する auditd はそうはいかない。全ての execve システムコールを監視し、Syslog経由で外部へ飛ばす設定が必須だ。
/etc/audit/rules.d/audit.rules への追記例:
# execve システムコールを監視し、すべてのコマンド実行をログに残す
-a always,exit -F arch=b64 -S execve -k command_execution
# ログが溜まったら即座にリモートログサーバ(rsyslog等)へ転送する設定を推奨
【対策2】Webアプリ側でのシェル起動の封じ込め
PHPなどのWebアプリケーション経由でコマンドが叩かれる場合、php.ini での制御が重要だ。そもそも「Webユーザがシェルを起動する必要があるか?」という問いに立ち返ろう。
; php.ini で危険な関数を無効化する
; これにより、Webシェル経由のコマンド実行を物理的に防ぐ
disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec
【対策3】Node.js環境でのセキュアな実装例
もしNode.jsで子プロセスを扱う必要がある場合でも、決してユーザー入力をそのまま child_process.exec に渡してはいけない。以下のサンプルは、コマンドインジェクションを防ぐためのセキュアな実装例だ。
const { execFile } = require('child_process');
/**
* ユーザー入力の直接実行を避け、引数を配列で分離して渡す
* これによりシェルを経由しないため、パイプやセミコロンによるコマンド連結が無効化される
*/
function runSafeCommand(userInput) {
const safeArgs = [userInput];
// execFile はシェルを起動しないため、コマンドインジェクションの脆弱性が激減する
execFile('/usr/bin/some_binary', safeArgs, (error, stdout, stderr) => {
if (error) {
console.error(`実行エラー: ${error.message}`);
return;
}
console.log(`結果: ${stdout}`);
});
}
—
4. 最後に:インシデントレスポンスの心構え
メモリフォレンジックは強力な武器だが、最大の防御は「攻撃者がメモリに痕跡を残す前に、システム上で何もできない状態を作ること」だ。
1. 最小権限の原則: Webサーバのプロセスは www-data で動かし、OSの主要な設定ファイルには書き込み権限を与えない。
2. 実行可否の監視: auditd や EDR を導入し、不審なバイナリ(curl や wget が突然起動するなど)を即座に検知する体制を作る。
3. 継続的なログ転送: ローカルのログは攻撃者に消されるものと想定し、収集した瞬間に別の管理サーバへ送信する。
「ログを消した」と安心している攻撃者を尻目に、我々はメモリという名の「確実な証拠」を掴んで追い詰める。技術者諸君、ログは消される前提で設計し、メモリの深淵を覗く勇気と準備を常に持っておいてほしい。
現場からは以上だ。また次のインシデント現場(もしくは技術検証の場)で会おう。
コメント