【実務・中級編】 メモリ上のコマンド履歴とシェルセッションの復元 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

メモリに刻まれた「悪意の足跡」を暴く:シェルセッション復元と防御の最前線

現場でインシデント対応をしていると、よく「ログを消したから大丈夫だ」と高を括っている侵入者に出会う。だが、彼らは致命的な見落としをしている。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. 継続的なログ転送: ローカルのログは攻撃者に消されるものと想定し、収集した瞬間に別の管理サーバへ送信する。

「ログを消した」と安心している攻撃者を尻目に、我々はメモリという名の「確実な証拠」を掴んで追い詰める。技術者諸君、ログは消される前提で設計し、メモリの深淵を覗く勇気と準備を常に持っておいてほしい。

現場からは以上だ。また次のインシデント現場(もしくは技術検証の場)で会おう。

コメント

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