攻撃の痕跡は生ログにある — 横移動・SQLi・事後調査の4視点

技術解説

暗号化は11分で終わった。

意味があるのは、その前の6日間です。その6日を、どのログから、どの順で読みますか。

痕跡は「1行」ではなく「並び」に残ります。

第6回では、疑いが上がった直後に何を見るかを扱いました。今回はその先——すでに起きたことを、後から筋道立てて示す側です。横移動の追跡、ランサムウェア事後調査、すり抜けたSQLi、個人データの棚卸し。目的も担当者も違いますが、4つとも同じ制約の下で動きます。原本を汚さず、手元から出さず、根拠つきで示す。

先に結論: UwView Pro のシーケンシャル検索は「w1 → w2 → w3 がこの順に現れる箇所」だけを拾うので、認証成功 → 権限昇格 → 別ホストへ接続 のような攻撃の並びをそのまま検索条件にできます。ドリルダウン検索は絞り込みを段で重ね、各段の件数を見ながら条件を替えられます。処理は手元のマシンで完結し、対象ログを外部へ送りません(実測値は特定環境での測定例で、環境により異なります。詳細は記事末尾)

本記事は「保全されたログが手元にある状態で、何をどう読むか」の話です。隔離・保全・届出・報告の手順そのものは、所属組織の規程と関係当局の指示に従ってください。


1. 横移動を追う — IDが途中で乗り換わる

状況

侵入起点の1台は分かりました。上から来る問いは決まっています。「どこまで広がりましたか。

手元には、VPNゲートウェイのログ、認証サーバのログ、踏み台の sshd ログ、ファイルサーバの監査ログ。それぞれ別の製品が、別の形式で書いています。同じ攻撃者の行動が、4つのファイルに分かれて記録されています。

なぜ起きるか

横移動は1台の中の異常ではなく、ログとログの「間」にしか現れないからです。

そして厄介なのは、追いかけるキーが途中で乗り換わることです。VPNのセッションIDでログインし、その先で認証チケットが発行され、そのチケットで共有フォルダにSMBセッションが張られる。3つのIDはどれも別の文字列で、直接つながる列はどこにもありません。つながっているのは「同じ時刻帯に、同じ送信元から、次のIDが立ち上がった」という事実だけです。

つまり判定条件は、語ではなく並びになります。VPN接続成功 → チケット発行 → 別ホストへの接続 → 共有フォルダの列挙。この順に並んでいれば横移動の形ですし、順が崩れていれば——たとえば共有フォルダの列挙がチケット発行より前なら——それは別人の通常業務です。第7回で書いたサーバ間の時刻ズレが、ここでは結論そのものを反転させます。

汎用ツールでの対処と限界

まず横断で拾い、時刻順に並べます。

grep -h -F 'S-1a9f3c' vpn.log auth.log sshd.log fileserver.log   # 判明しているID
# 見つかった時刻帯を、4本まとめて時系列にする(列位置は環境ごとに調整)
sort -m -k1,2 <(grep '2026-08-2[0-6]' vpn.log) <(grep '2026-08-2[0-6]' auth.log) | less
# その時刻帯に立ち上がった別のIDを拾い直す
awk '$1 >= "2026-08-24T02:10" && $1 <= "2026-08-24T02:40"' auth.log | grep -o 'TGT=[A-Za-z0-9]*' | sort -u

3本目が、この作業の実体です。見つけたIDで検索 → その時刻帯を読む → 新しいIDを拾う → そのIDでまた検索。連鎖の段数ぶん、これを繰り返します。

限界は3つ。ひとつめ、この往復のたびに原本を頭から読み直すことです。段が5つあれば5往復、外れた仮説を含めれば十数回。1回が数十秒でも、事後調査は「どこまで広がったか」を確定させるまで終われません。

ふたつめ、4本を同時に見られないことです。sort -m で1本にまとめると読めますが、まとめた瞬間にどの行がどのファイル由来か分からなくなる。本当にやりたいのは、4本を開いたまま同じ時刻帯を行き来することでした。

みっつめ、grep は順序を表現できないことです。grep -E 'VPN|TGT|SMB' は3語のどれかを含む行を返すだけで、並びは見ません。awk で状態機械を書けば書けますが、乗り換わるIDを追う調査では当たりを付けるたびに書き捨てのスクリプトを書くことになり、初動の速度に合いません。


2. ランサムウェア事後調査 — 見たいのは暗号化の「直前」

状況

暗号化は終わっています。復旧はバックアップから進んでいます。並行して、別の宿題が来ます。「いつから入られていたか。何を持ち出されたか。

本番機は隔離済み。手元にあるのは、保全されたファイルアクセスログと監査ログのコピーです。数十GB、しかも大半は .gz のまま。

なぜ起きるか

暗号化フェーズは短く、価値のある記録はその手前の静かな数日にあるからです。

暗号化そのものは大量の書き込みを出すので、ログ上では極端に密な帯になります。時間軸で見ると、事件全体のうちごく一部だけが真っ黒に潰れている。読みたいのはその黒い帯ではなく、その前——探索、大量の読み取り、アーカイブの作成、外向きの転送——です。そこは平時のログと見た目がほとんど変わりません。

そして事後調査には、障害調査にはない制約が2つ乗ります。ひとつは保全。原本に書き込む操作、原本を上書きする加工は使えません。もうひとつは置き場所。被害を受けたシステムのログを、外部のサービスへアップロードして解析するという選択肢は、契約や規程の面で取れないことがあります。手元のマシンで完結する必要がある、というのは事後調査ではしばしば要件です。

汎用ツールでの対処と限界

圧縮したまま探すのが定石です。

zgrep -h 'READ\|COPY' audit-2026-08-*.log.gz | head -100
# 暗号化開始の直前24時間へ範囲を絞る
zcat audit-2026-08-24.log.gz | awk '$1 >= "2026-08-24T00:00" && $1 <= "2026-08-25T03:12"' | less
zcat audit-*.gz | awk '{print $5}' | sort | uniq -c | sort -rn | head -20   # 誰が多く触ったか

zgrep は正しい道具です。展開したファイルをディスクに置かずに済むので、保全の観点でも筋がいい。

限界は3つ。ひとつめ、条件を変えるたびに毎回展開し直すことです。zgrep は検索のたびに解凍を通します。事後調査は仮説を次々に替える作業なので、この解凍が回数ぶん積み上がります(第8回で扱った圧縮保管と検索の両立が、そのまま効いてきます)。

ふたつめ、「直前」の幅を事前に決められないことです。24時間で足りるのか、6日必要なのかは、開いてみるまで分かりません。awk の範囲指定は開く前に決めろと言い、決め直すたびに全走査です。

みっつめ、判定したいのがまた順序であることです。大量の読み取り → アーカイブ作成 → 外部への接続 の並びが揃ってはじめて持ち出しの形になります。バラバラの順で出ている同じ3語は、たいていバックアップジョブです。


3. すり抜けたSQLi — WAFのログに残らないもの

状況

WAFは入っています。だからブロックの記録はあります。それでも調べるのは、ブロックされなかったリクエストです。それはWAFのログには載らず、Webサーバのアクセスログの生データにしかありません。

なぜ起きるか

攻撃側が、パターン照合を外すために表記を変えてくるからです。

URLエンコード、二重エンコード、コメントによる分断(UNION/**/SELECT)、大文字小文字の混在、改行や制御文字の挿入。同じ意味の文字列が、ログの上では何十通りにも見えます。だから正規表現は必ず「書いては直す」ものになります。

そして書いた瞬間に、逆側の問題が出ます。誤検知です。 サイト内検索の q=or が入る、記事タイトルに union が入る、select は普通の英単語です。ヒットの大半は無害で、無害だと確認する作業は1件ずつ文脈を読むことになります。

さらに、「試行があった」と「成功した」は別の話です。成功したかどうかは、同じ行の別のフィールド(ステータスコード、レスポンスサイズ)と、その後に続く行(同じIPからの不自然に整った連続リクエスト)の関係に出ます。

汎用ツールでの対処と限界

まず広めに拾い、そこから絞ります。

grep -Ei "union[[:space:]/*]+select|'[[:space:]]*or[[:space:]]*'1'?=|sleep\(|benchmark\(" access.log | head -50
# URLデコードしてから照合する(多重エンコード対策に2回通す)
perl -pe 's/%([0-9A-Fa-f]{2})/chr(hex($1))/ge' access.log | grep -Ei 'union.*select' | head
# ヒットしたIPが、200を受け取っているか
grep '203.0.113.77' access.log | awk '{print $9, $10}' | sort | uniq -c

2本目のデコードは効きます。多重エンコードは、通す回数を増やすだけで見えるようになります。

限界は3つ。ひとつめ、デコードした時点で、それはもう原本ではないことです。パイプの先に流れているのは加工後のテキストで、原本の行番号は付いていません。「この行を報告書に引用する」段になって、原本のどこだったかを探し直すことになります(第9回の「加工した先はもう原本ではない」と同じ形です)。

ふたつめ、正規表現の調整が全読みの繰り返しになることです。誤検知を減らす作業は、条件を足しては外しての10回20回です。そのたびに数GBを頭から走査します。しかも段ごとに何件減ったのかが残らないので、どの条件が効いたのかが分かりません。「9.5万件 → 184件 → 54件」のように減り方が見えれば一目なのですが、パイプの途中経過は流れて消えます。

みっつめ、成功の判定が単発の grep では書けないことです。ステータスコードは同じ行、後続の挙動は別の行。1本のパイプで両方を見ることはできず、結局ヒット一覧と現物のあいだを往復することになります。


4. 個人データの棚卸し — 1人が1つのキーで表せない

状況

削除請求が届きました。あるいは漏えいの疑いがあり、影響を受けた対象者を特定する必要があります。求められているのは「この人の痕跡を、残らず出してください」。期限があります。

ログは数年分、数十GB、複数システムにまたがっています。

なぜ起きるか

1人の人間が、ログの中では複数の別々の文字列として現れるからです。

会員ID、メールアドレス、変更前のメールアドレス、電話番号、セッションID、そのとき使っていたIPアドレス。どれか1つで検索しても、他の表記で書かれた行は残ります。しかも厄介なことに、キーは調査の途中で増えます。 メールアドレスで引いた行の中に、知らなかったセッションIDが書いてある。そのIDで引き直すと、また別の識別子が出てくる。

そして仕事の性質が、第13回の全量検品と同じです。「無い」ことを示すために、全部を通さなければならない。 「見つかりませんでした」と回答するには、どの範囲をどの条件で確認したかを言えなければなりません。

制約もひとつ増えます。対象は個人データそのものです。外部のサービスへ上げて探す、という手が原理的に取りにくい。

汎用ツールでの対処と限界

キーの一覧を作って、まとめて当てます。

grep -h -F -f keys.txt access.log auth.log app.log | tee hits.txt | wc -l
# ヒットした行から、まだ知らない識別子を回収する
grep -o -E 'sid=[A-Za-z0-9]{16}|[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+' hits.txt | sort -u
# ファイル名と行番号つきで残す(報告書用)
grep -n -H -F -f keys.txt *.log > evidence.txt

grep -F -f は、この用途の主力です。数十個のキーを固定文字列として一度に当てられます。3本目の -n -H で座標を残すのも、報告書を書く側からすれば必須の作法です。

限界は2つ。ひとつめ、キーが増えるたびに全部やり直しになることです。新しい識別子が1つ見つかるたび、keys.txt に足して数十GBを頭から走り直す。これが数回で終わればいいのですが、識別子の連鎖は途中で分岐します。

ふたつめ、見た範囲と条件が残らないことです。端末の履歴は閉じれば消えます。回答期限の直前に「どの条件で確認したか」をまとめる段になって、もう一度やり直した、という話は珍しくありません。検索条件そのものが成果物である、という仕事はログ調査では少なくないのに、汎用ツールはそれを保存する場所を持っていません。


4つに共通していたもの

視点 探しているもの 制約 汎用ツールが止まる場所
横移動 乗り換わるIDの連鎖 4本を同時に追いたい 往復のたび全読み。順序を書けない
ランサム事後調査 暗号化の「直前」 保全・手元で完結 条件を変えるたび展開し直し。幅を先に決められない
すり抜けたSQLi 試行と、成功したかの判定 引用には原本の座標が要る デコードで座標が消える。段ごとの件数が残らない
個人データの棚卸し 1人を指す複数のキー 外部へ出せない・期限がある キーが増えるたびやり直し。条件が残らない

2列目はばらばらです。3列目もばらばらです。それでも4列目が揃っているのは、この4つが同じ3つの操作を要求しているからです。

  • 順序で探すVPN成功 → チケット発行 → 別ホスト大量読み取り → アーカイブ → 外部接続。攻撃の痕跡は語の集合ではなく、語の並びに現れます。
  • 絞り込みを重ね、段ごとの件数を見る — 正規表現の調整も、キーの追加も、1回では終わりません。減り方が見えれば、効いた条件がその場で分かります。
  • 原本の座標を保ったまま、手元で完結する — 引用にはファイル名と行番号が要り、対象データは外へ出せない。加工したコピーではなく、原本のまま扱えること。

3つとも、小さいファイルなら誰でもやっていることです。それが数十GBになった途端、往復のたびに全読みを要求され、デコードすれば座標が消え、条件は端末を閉じると消える。操作が難しいのではなく、大きさのせいで割に合わなくなる。 連載でずっと同じことを書いていますが、事後調査ではこれに「間違えたときの代償」と「期限」が上乗せされます。

冒頭の11分に戻ります。その11分は、どんな道具でも見つかります。見つからないのは、その前の6日です。


使っている道具

私が開発している UwView(無料)は、巨大なテキストを開いた瞬間から表示・スクロール・検索できるビューアです。索引はバックグラウンドで作られ、完成すると行番号が付きます。分割も抽出もしないので、保全したログを1本のまま・無改変のまま扱えます。原本には書き込みません(フォレンジックの第一原則)。ハイライトは行を捨てずに色を付けるので、第3章の「無害だと確認した行を消さずに脇へ置く」もそのままです。処理はすべて手元のマシンで行い、ファイルを外部へ送信しません。

そして、上に並べた3つの操作をそのまま持っているのが UwView Pro です。

  • シーケンシャル検索: w1 → w2 → w3この順で現れる箇所だけを拾います。第1章の VPN成功 → チケット発行 → 別ホストへ接続、第2章の 大量読み取り → アーカイブ作成 → 外部接続 が、そのまま検索条件になります。右クリックの「履歴」で成立した順序の経路そのもの(何行目の何 → 何行目の何)が見え、どの行へもジャンプできます。ただし各段は前段の位置を開始点に本文を検索するため、所要時間は全文検索とほぼ同じです。→ シーケンシャル検索の記事
  • 多段階検索(ドリルダウン): 検索結果をさらに別の語で絞り込みます。タブに 語(件数) が並ぶので、第3章の正規表現の調整や第4章のキー追加が段ごとの件数を見ながら進みます。2段目以降は前段の範囲だけが対象なので待ちがなく、条件を戻したいときはその段のタブに戻るだけです。→ 多段階検索の記事
    この2つが実際にどう見えるかが、下の画面です。

UwView Proの多段階検索で結果一覧の行を右クリックし「履歴(絞り込みの経路)」を開いた画面。上部のタブに Tokyo (10,967) → Ariake (7) → 有明 (4) と件数が並び、サブメニューには「行 320,574,475: Tokyo」「行 320,574,475: Ariake」と、その行に至るまでに通った各段の語と原本の行番号が表示されている。総行数8.9億行のファイル

上のタブが Tokyo (10,967) → Ariake (7) → 有明 (4)件数付きで並んでいるのが第3章の「段ごとに何件減ったか」、右クリックの 「履歴(絞り込みの経路)」行 320,574,475: Tokyo → 行 320,574,475: Ariake語と原本の行番号が順に並んでいるのが第1章の「どの語で、どこから来たのか」です(画面は8.9億行のOSMデータ。ログでも同じ形で使えます)。

grep のパイプと比べると、残るものが違います。

grep A file \| grep B \| grep C 多段階検索+履歴
段ごとの件数 残らない(最終結果だけ) タブに 語(件数) が並ぶ
原本の行番号 2段目以降は消える(grep -n が数えるのは流れてきたブロック内の番号) 320,574,475 のまま保持され、その行へジャンプできる
その行がどの語で拾われたか 分からない 「履歴」に語と行番号が順に並ぶ
条件をやり直すとき パイプを書き直して原本を最初から読み直す 戻りたい段のタブをクリックするだけ
調査を中断したとき 端末を閉じれば条件も経路も消える タブと履歴が残り、翌日そこから再開できる

第4章の「見た範囲と条件が残らない」は、この表の1行目と5行目のことです。検索条件そのものが成果物になる仕事——棚卸しの回答も、事後調査の報告も——では、この差がそのまま作業日数と、報告書の書きやすさに出ます。

  • ±N は段ごとに独立: 絞る段は±1で締めて、読む段だけ±10に広げる。Nを変えても原本は読み直しません。第2章の「直前の幅を先に決められない」に効きます。
  • 索引と圧縮を保存する: 2回目以降は行番号付きで瞬時に開けます(47.73GBのテキストで実測0.02〜0.07秒。特定環境での測定例で、環境により異なります)。圧縮キャッシュ経由の検索と約1/9保管も付くので、保管義務のある古いログを抱えたままでもディスクが楽になります(全OS対応・買い切り/月額プランあり)。第2章のように保全した数十GBを何日も開き直す作業では、この差が日数に効きます。

正直に書いておくと、UwView はビューアであって、SIEM でもフォレンジックスイートでもありません。複数ログの自動相関、脅威インテリジェンスとの突き合わせ、アラート、レポート生成——どれもしません。第3章の正規表現を代わりに考えてくれるわけでもなく、第1章のタイムスタンプ正規化も、ファイル間の自動結合もしません(並べて見るのは人間の目です)。ディスクイメージやメモリダンプの解析も対象外です。担うのは、その前段——保全した生ログを、そのままの形で、手元で人が見るところだけです。相関とアラートが要る規模なら、そちらの製品が正解です。

なお、原本を書き換えない差分編集(Edit Upgrade)は別ライセンスとして用意していますが、本記事の4視点はいずれも読むだけの作業なので、必要になるのは View 側の機能です。

リンク

  • 第1回: 巨大ファイルに沈む4つの定番ツールと、その先: https://uvp.y42u.net/blog/uwview-ps01-huge-file-tool-limits/
  • 第2回: ログの因果を遡る4つの技法: https://uvp.y42u.net/blog/uwview-ps02-log-causality-tracing/
  • 第4回: 消す・残す・圧縮するの判断基準: https://uvp.y42u.net/blog/uwview-ps04-log-retention-decision/
  • 第6回: 認証ログ数千万行から痕跡を拾う4手順: https://uvp.y42u.net/blog/uwview-ps06-intrusion-triage-auth-logs/
  • 第7回: タイムスタンプを武器にする4つの読み方: https://uvp.y42u.net/blog/uwview-ps07-timestamp-driven-triage/
  • 第8回: 圧縮保管と検索の両立: https://uvp.y42u.net/blog/uwview-ps08-compressed-archive-search/
  • 第9回: 構造化データを生のまま読む4場面: https://uvp.y42u.net/blog/uwview-ps09-read-raw-structured-data/
  • 第13回: 巨大データ検品の4技法: https://uvp.y42u.net/blog/uwview-ps13-huge-data-inspection/
  • 並び順を条件にして探す: https://uvp.y42u.net/blog/uvp-sequence-search/
  • 絞り込んだ先からさらに絞り込む: https://uvp.y42u.net/blog/uvp-drilldown-search/
  • nginxアクセスログの5xxを追う: https://uvp.y42u.net/blog/uwview-access-log-5xx-workflow/
  • ソースコード(GitHub): https://github.com/amru195704/UwView

開発者より: アプリ・Kindle本・オープンソースの一覧は GitHub: amru195704 にまとめています。


お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。実際のインシデント対応・証拠保全・個人データの取り扱いは、所属組織の規程および関係法令・当局の指示に従ってください。掲載した時間・件数・識別子は説明のための例であり、特定の実在する事案を示すものではありません。IPアドレスはすべて文書用の例示アドレスです。ログの形式・フィールド位置・時刻表記は、OS・ミドルウェア・製品・設定により大きく異なります。コマンド例は環境(GNU/BSD、シェル、awkgrepzgrep の実装差等)により調整が必要です。記載した実測値は特定の1環境での測定例であり、同じ結果を保証するものではありません。誤記・不正確な情報がございましたら、コメント欄よりご指摘いただければ、確認のうえ修正いたします。

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