【実務・中級編】 Windowsイベントログとメモリ情報の相関分析による攻撃者の行動追跡 – インシデントレスポンス & フォレンジック(DFIR)防御ガイド

おい、ちょっとこっちに来てくれ。

昨夜、ウチの監視システムが奇妙なアラートを吐いた。某WebサーバーのCPU使用率が跳ね上がり、不可解なプロセスが外部と通信しようとした痕跡があったんだ。幸い、初期段階でネットワークから隔離したから致命傷は免れたが……さて、ここからがインシデントレスポンスの腕の見せ所だ。

「攻撃者はどうやって侵入し、システム内部でどう動いたのか?」

これを正確に暴くには、イベントログだけを見ていてもダメだし、メモリのダンプだけでも断片的すぎる。Windowsイベントログとメモリ情報、この二つを突き合わせて初めて、攻撃者の足跡が一本のリアルなタイムラインとして浮かび上がってくる。

今日は、現場の最前線で俺たちがいつもやっている「ログとメモリの相関分析」のイロハを、実際のインシデントシナリオに沿って叩き込んでやる。心して聞け。

—

1. なぜ「ログ」と「メモリ」の両方が必要なのか?

インシデント対応の現場でよくある勘違いが、「Event ID 4688(プロセスの作成)」が残っているから大丈夫、というものだ。

甘い。攻撃者はログを改ざんしたり、そもそもログに残らない手法(メモリ上だけで完結する Fileless Attack やプロセスインジェクション)を使ってシステムを踏み台にする。逆に、メモリ(RAM)は今のシステムの「スナップショット」に過ぎず、過去に何が起きていたかの全体像を追うのは難しい。

だからこそ、「証拠の永続性があるが改ざんされるリスクもあるログ(Event ID 4688等)」 と 「今の真実を語るが揮発性の高いメモリ」 をクロスリファレンス(相関分析)させる必要がある。

例えば、こんなシナリオを考えてみよう。
1. Webアプリケーションの脆弱性(RCE)を突かれ、Webサーバーのプロセス(w3wp.exe や httpd.exe)から cmd.exe がキックされた。
2. 攻撃者は痕跡を消すためにイベントログのサービスを停止するか、ログをクリアしようとした。
3. しかし、メモリ上には、そのコマンドが実行された瞬間のプロセス構造(PPID: 親プロセスID)や、難読化されたPowerShellのコマンドライン引数が生々しく残っている。

これを紐解くのが、俺たちの仕事だ。

—

2. 現場で使う基本の武器:Event ID 4688 と Volatility

まずは、調査の軸となる2つの要素を確認しておこう。

① Windowsセキュリティイベントログ:Event ID 4688

プロセスが作成されたときに記録されるイベントだ。特に A new process has been created. というログの中にある以下のパラメータが重要になる。

  • SubjectUserName: 実行したユーザー
  • NewProcessId: 生成されたプロセスのPID(16進数で記録されることが多いので注意しろ)
  • ProcessCommandLine: 実行されたコマンドライン引数
  • CreatorProcessId: 親プロセスのPID(PPID)

※注意:デフォルトでは ProcessCommandLine は記録されないことが多い。グループポリシー(GPO)で「プロセス作成時のコマンドラインを含める」を有効にしていない現場は、今すぐ設定を見直せ。

② メモリフォレンジックツール:Volatility 3

メモリダンプ(.raw や .dmp)からプロセスツリーやネットワーク接続を復元するためのデファクトスタンダードツールだ。これを使って、イベントログの裏付けを取る。

—

3. 実践:ログとメモリを突き合わせるタイムライン構築の手順

ここからが本題だ。実際のインシデント調査を想定して、手順を追っていこう。

ステップ 1: イベントログから怪しいプロセスの「点」を見つける

まずはPowerShellなどを使って、不審なプロセス起動ログ(Event ID 4688)を抽出する。例えば、IISやApacheなどのWebサーバープロセスから、直接 cmd.exe や powershell.exe が呼び出されているログを探す。

# 特定の不審な子プロセス起動イベントを抽出するPowerShellスクリプト例
Get-WinEvent -FilterHashtable @{LogName='Security'; ID=4688} | 
    Where-Object { $_.Message -match "cmd.exe|powershell.exe|certutil.exe" } |
    Select-Object TimeCreated, 
                  @{Name='ProcessName';Expression={ $_.Properties[5].Value }},
                  @{Name='CommandLine';Expression={ $_.Properties[8].Value }},
                  @{Name='ParentProcessName';Expression={ $_.Properties[13].Value }}

これで「いつ、どの親から、どんな引数で子プロセスが起動されたか」のリスト(点)ができる。

ステップ 2: メモリダンプからプロセスツリーの「線」を検証する

次に、取得しておいたメモリダンプに対して Volatility 3 を実行し、実際のメモリ上のプロセス構造を確認する。イベントログが改ざんされていたり、隠蔽されていても、メモリ上には真実が残っている。

# Volatility 3 を使ってプロセスツリー(windows.pstree)を表示するコマンド例
python3 vol.py -f suspicious_memory.raw windows.pstree

出力結果を見ると、次のような構造が見えてくるはずだ。

PID   PPID  ImageFileName
4120  2840  w3wp.exe
5892  4120  cmd.exe        <-- Web経由で実行された痕跡
6012  5892  powershell.exe <-- 難読化されたスクリプトを実行中

ここで、イベントログのタイムスタンプと、メモリ上のプロセスの作成時間・PPID関係を突き合わせる。もしイベントログに記録されていないプロセスがメモリ上に存在していれば、それはログアウトされた痕跡、あるいは高度なアンチフォレンジック(DKOMなど)を受けた証拠だ。

—

4. 予防と対策:攻撃の起点を断つセキュアな設計

フォレンジックで原因を特定したところで満足するな。プロのエンジニアなら、「二度と同じ手法で侵入されないための仕組み」をコードやインフラ設定に落とし込むまでが仕事だ。

今回のインシデントの起点が「Webアプリケーション経由での任意コード実行(RCE)」だった場合を想定し、PHPとNginx、そしてOSレイヤーでの鉄壁の防御設定を共有する。コピペしてそのまま本番環境に適用できるようにしておけ。

① PHPアプリケーション側での危険な関数の無効化 (php.ini)

攻撃者はRCEから system() や exec() を叩いて外部からペイロードをダウンロードしようとする。これらをPHPの設定レベルで完全に封殺する。

; =====================================================================
; [Security] 危険なシステムの実行関数の無効化
; =====================================================================
disable_functions = exec, passthru, shell_exec, system, proc_open, popen, curl_exec, curl_multi_exec, parse_ini_file, show_source

; リモートファイルインクルージョン(RFI)の防止
allow_url_fopen = Off
allow_url_include = Off

; エラーメッセージの画面出力禁止(情報漏洩の防止)
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log

② Nginxでの不審なリクエストの遮断 (nginx.conf)

Webシェル(攻撃者が仕掛けたバックドアスクリプト)への直接アクセスや、URLエンコードされた怪しいパラメータをWAF的に弾く設定だ。

server {
    listen 80;
    server_name vulnerable-app.local;

    root /var/www/html;
    index index.php index.html;

    # =====================================================================
    # [Security Headers] 基本的なセキュリティヘッダーの付与
    # =====================================================================
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # =====================================================================
    # [Access Control] アップロードディレクトリ等でのPHP実行を完全に禁止
    # =====================================================================
    location ~* /uploads/.*\.php$ {
        deny all;
    }

    # クエリパラメータに怪しいコマンドインジェクションの兆候がある場合は即座に403を返す
    if ($query_string ~* "(base64_encode|eval|system|passthru|cmd\.exe|powershell)") {
        return 403;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        
        # 外部からの予期せぬ変数のインジェクションを防ぐ
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
    }
}

—

5. 最後に:インシデントレスポンスは「事前の備え」が9割

今回の調査手法、そして予防のための設定、しっかりと頭に叩き込んでもらえただろうか。

実際の現場では、パニックになりがちだが、「ログという過去の足跡」と「メモリという現在の真実」を冷静に突き合わせることで、攻撃者のストーリーは必ず見えてくる。そして、何より大切なのは、インシデントが起きた後に慌てるのではなく、あらかじめ堅牢なコードを書き、適切なログ監査ポリシー(Event ID 4688のコマンドライン有効化など)をインフラ全体に適用しておくことだ。

セキュリティは「魔法の杖」ではなく、日々の泥臭いエンジニアリングの積み重ねでできている。ウチのチームのコードとインフラは、俺たちが守る。頼んだぞ。

コメント

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