証拠としてのログ — 保全・切り出し・改ざん対策の4原則

技術解説

再起動で38分が消えた。

直したのは正しい。では、直す前に何を持ち出すべきだったのでしょう。

失われ方には型があります。4つです。

前回までは「どう読むか」を書いてきました。今回はその前——読む対象を、あとで人に見せられる形で手元に残す話です。再起動、ローテーション、報告書への切り出し、そして「改ざんされていない」の示し方。どれも派手さはありませんが、失敗すると調査そのものが成立しなくなるという点で共通しています。

先に結論: UwView は原本に書き込みません。開いた瞬間から表示・検索でき、分割も切り出しもしないので、保全したログを1本のまま・無改変のまま読めます。Pro の多段階検索は絞り込みを段として重ね、原本の行番号を最後の段まで保持するので、報告書に「何行目」と書ける形で該当箇所を引用できます。処理はすべて手元のマシンで完結し、対象ログを外部へ送りません(実測値は特定環境での測定例で、環境により異なります。詳細は記事末尾)

本記事は「保全したログが手元にある/これから手元に置く」段階の話です。封じ込め・届出・法的な証拠能力の判断そのものは、各組織の規程と所管の指針に従ってください。


1. 「再起動したら直った」の前に — 消えるものと残るものを分ける

状況

深夜、サービスが応答しません。原因は分かりませんが、再起動したら直りました。

翌朝、報告を求められます。「原因は?」——答えられません。ログを見に行くと、知りたかった時間帯のものが無い。あるいは、あるのに肝心の数分だけが欠けています。

なぜ起きるか

再起動は「消す操作」だからです。ただし、何もかもを消すわけではなく、消えるものと残るものがはっきり分かれています。

再起動でほぼ確実に消えるもの:

  • プロセス内のログバッファ — 多くのロギングライブラリは行ごとに write せず、まとめて書きます。強制終了(電源断・パニック・強制停止シグナル)では、まだディスクへ渡していないぶんが消えます。最後の数秒から数十秒、つまり障害の核心部分がここに入ります
  • カーネルのリングバッファdmesg が読んでいる領域は再起動で新しいものに置き換わります(ディスクへ落とす設定をしていなければ)
  • メモリ上の一時領域 — tmpfs に置かれたディレクトリは中身ごと消えます。journald が Storage=volatile(既定が auto/var/log/journal が無い場合を含む)なら、ジャーナルもメモリ上の実行時領域にあり、再起動と同時に消えます
  • 揮発的な状態 — ソケットの一覧、プロセスツリー、開いているファイルディスクリプタ、消されたのに開かれ続けているファイル。これらはログではありませんが、しばしばログより雄弁です

残るもの: ディスクに書き終わった /var/log/*Storage=persistent のジャーナル、ローテート済みの圧縮ログ。

つまり「再起動したら直った」で失われるのは、ちょうど原因に近い側です。

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

再起動の前に、数十秒でできることがあります。

# 退避先のディレクトリを1つ用意しておく(例: preserve-20260910T031500Z)
D=preserve-20260910T031500Z

dmesg -T                       > "$D/dmesg.txt"
journalctl -b -o short-precise > "$D/journal-thisboot.txt"
ps auxww                       > "$D/ps.txt"
ss -tanp                       > "$D/ss.txt"
cp -a /var/log/app/current.log "$D/"          # コピー。移動でも切り出しでもない

これに加えて、開いているファイルディスクリプタの一覧を出すコマンドがあるなら、その出力も取っておきます。そしてアプリを止める必要があるなら、まず正常終了を試す——終了シグナルを受け取れば、多くの実装は残りのバッファを吐いてから落ちます。強制停止はそれが効かないときの最後の手段です。

cp であって mv でないこと、tail で切り詰めていないことが要点です。この段階では「読める大きさに縮める」ことを絶対にしない。縮める判断は、あとから何度でもやり直せます。縮めてから「その前を見たい」となったときには、もう戻れません。

限界は3つ。

ひとつめ、全部取ると読めない大きさになること。journalctl -b が数十GBになる環境は珍しくありません。取れました、しかし開けません——これは第1回の壁にそのまま戻る話です。

ふたつめ、退避先の容量。同じディスクに置けば本番の空きを削り、最悪、障害を深くします。かといって別ディスクへコピーする時間は、障害中のいちばん惜しい時間です。

みっつめが、実務でいちばんよく効きます。深夜2時に、この手順を思い出せるかどうか。手が震えている人間の記憶力に依存する手順は、無いのと同じです。だから第10回に書いたとおり、これはスクリプトにして置いておくべきものです——preserve.sh を1本、実行パスの通ったところに。


2. ローテーションの狭間 — 証拠が消える瞬間は、あらかじめ予定されている

状況

障害は 03:24 に起きました。翌日、ログを見に行きます。

app.log はあります。ただし中身は 03:25 以降しかありません。03:24 のぶんは app.log.1.gz に……入っているはずでした。実際に開くと、その数分が丸ごと抜けています。あるいは、8世代設定の rotate 8 で、そもそも該当ファイルが消えていました。

なぜ起きるか

ローテーションは、ファイルを差し替える操作だからです。差し替えには必ず一瞬の隙間があり、方式によって隙間の大きさが違います。

logrotate の2つの方式を分けて見ます。

  • create 方式(既定): 元ファイルを mv してから新しいファイルを作り、postrotate でアプリに再オープンさせます(HUP シグナルを送るのが定番)。アプリが再オープンに失敗すると、アプリは mv された側の inode に書き続けます。ディレクトリを見ると app.log は空のまま、実体は app.log.1 に流れ込んでいる——見た目上「ログが止まった」ように見える障害の定番です
  • copytruncate 方式: 中身をコピーしてから元ファイルを truncate します。アプリを触らずに済むので楽ですが、コピーと truncate の間に書かれた行は、どちらにも残りません。数GBのコピーには秒単位かかります。秒間数千行なら、消えるのは数千〜数万行です

さらに、消える予定は設定ファイルに書いてあります。rotate 8 + daily なら8日、maxage 30 なら30日。監査で「3年ぶん」と言われている一方、実機の設定は8日、というズレは実際によくあります(第4回で扱った「消す・残す・圧縮する」の判断が、ここで結果として現れます)。

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

まず、何がいつ消えるのかを実行前に見ます。

logrotate -d app.conf                    # dry-run。実行せずに動作だけ表示
grep -rn 'rotate\|maxage\|copytruncate\|create' logrotate.d/

設定ファイルの置き場所はディストリビューションの既定に読み替えてください(多くは logrotate.d 配下と、その親の全体設定ファイル)。前回いつ回ったかは、logrotate が持つ状態ファイル(logrotate/status)に記録されています。

そのうえで、回る直前に退避を挟みます。

/var/log/app/*.log {
    daily
    rotate 8
    dateext
    dateformat -%Y%m%d
    delaycompress
    sharedscripts
    prerotate
        cp -a /var/log/app/app.log /archive/app-20260910T031500Z.log
    endscript
    postrotate
        # アプリに再オープンを通知する(サービスの reload、または HUP シグナル)
    endscript
}

退避先のファイル名の日時は、実際には日時取得コマンドの展開で生成します(例では固定の値を書いています)。

dateext は地味ですが効きます。app.log.1app.log.2 と番号がずれていく方式では、同じ名前が別の中身を指すため、あとから「この app.log.3 はいつのものか」を追えなくなります。日付を名前に持たせれば、名前が座標になります。

限界は3つ。

ひとつめ、prerotatecp はローテーション自体を長くすること。50GBのコピーには数分かかり、その間ディスクI/Oが飽和して本番が遅くなります。深夜のバッチ時間帯とぶつかれば、退避が原因で別の障害を作りかねません。

ふたつめ、容量が単純に2倍になること。圧縮して置きたくなりますが、圧縮したログを zgrep で探すのは遅い——第8回で書いたとおり、展開しながらの逐次走査になるからです。

みっつめが、証拠として見たときにいちばん重い。cp した時点で、それは別のファイルになること。cp -a は mtime と atime を保ちますが、コピー先の inode は新しく、ctime(inode の変更時刻)は現在時刻になります。「このファイルは 03:24 に書かれたものです」の根拠として mtime を出しても、mtime は touch で任意に設定できます。ファイルの属性は、それ自体では何も証明しません。ここから4章に繋がります。


3. 報告書に貼る「±10行」 — 原本を汚さずに切り出す

状況

原因の行が見つかりました。「該当ログを報告書に貼っておいて」と言われます。前後10行あれば足ります。

sed -n で切り出して貼ります。レビューで「この直前は何をしていた?」と聞かれ、sed からやり直します。次は「20行前から」。また sed。3周目で、貼った断片のどれが何行目だったのか分からなくなります。

なぜ起きるか

切り出しは、3つのものを同時に壊すからです。

  1. 原本の座標(行番号)。切り出したファイルの1行目は「1行目」であって、12,034,541行目ではありません
  2. 文脈の幅を決めるタイミング。何行必要かは、読んでみるまで分かりません。にもかかわらず、切り出しは幅を先に要求します
  3. 同一性。切り出した瞬間、それは原本ではなくなります。ハッシュは一致せず、「この断片が原本の一部である」ことを、断片だけからは示せません

3つめが効いてくるのは、報告書が社外に出るときです。断片に「12,034,541行目から21行」と書いてあっても、原本を持っていない読み手には検証できません

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

座標を保ったまま切り出す方法はあります。

# 原本の行番号を付けたまま前後を出す(-n が効く)
grep -n -C 10 'OutOfMemoryError' huge.log > slice.txt

# 行番号が分かっている場合
awk 'NR>=12034531 && NR<=12034551 {printf "%d: %s\n", NR, $0}' huge.log

# 断片ではなく「どこを見たか」を記録する(座標だけを残す)
grep -n 'OutOfMemoryError' huge.log | cut -d: -f1 > hit-lines.txt

1本目の grep -n -C が実務では最有力です。原本の行番号が付き、前後も出ます。

それでも限界は残ります。

ひとつめ、-C の値を打つ前に決めなければならないこと。10では足りないと分かるのは読んだあとで、-C 20 に変えるとファイルを頭からもう一度読みます。第15回に書いたI/O律速の下では、この読み直しが往復ごとに積み上がります。

ふたつめ、ヒットが多いと使い物にならないこと。100ヒット × 21行 = 2,100行に -- の区切りが混ざったものが出てきます。報告書に貼れる形ではありません。かといって -m 1 で絞ると、こんどは「他にも出ていたのか」が分からなくなります。

みっつめ、行番号を本文に前置きすると、そのテキストは原本の行そのものではなくなること。12034541: [ERROR] ... は読みやすいですが、この文字列を検索しても原本には見つかりません。座標を残すために本文を汚す、というトレードオフです。

そして根本的な限界がひとつ。断片は、原本の存在を前提にしてはじめて証拠になります。 報告書に必要なのは断片そのものではなく、「原本がここにあり、これはその何行目である」という対応関係です。その対応関係を担保するのが、次の章の話です。


4. 「改ざんされていない」をどう示すか — チェックサムの効く範囲

状況

保全したログのハッシュを取りました。

sha256sum evidence.log > evidence.log.sha256

これで安心、のはずでした。ところが監査で「そのハッシュはいつ・誰が取ったものですか」と聞かれ、答えに詰まります。

なぜ起きるか

チェックサムが証明するのは「2つのバイト列が同じである」ことだけだからです。

それ以外は、何ひとつ証明しません。

  • いつ取ったか.sha256 ファイルの mtime は書き換えられます。中に日時を書いても、その文字列も書き換えられます
  • 誰が取ったか — ハッシュ値には署名者がいません
  • ハッシュ自体の真正性evidence.logevidence.log.sha256 が同じディレクトリにあるなら、ログを書き換えられる権限を持つ人は、ハッシュも同じく書き換えられます。同じ場所に置いたハッシュは、鍵を挿したままの錠前です
  • 中身が真実であること — アプリが嘘の行を書いていたら、ハッシュは正しく一致します

つまり sha256sum 単体が言えるのは「このハッシュを取った時点より後、私の手元で壊れていない」だけです。改ざん耐性はハッシュ関数の強度ではなく、ハッシュをどこに置くかで決まります。

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

やることは「置き場所を分ける」と「時刻と主体を足す」の2つです。

# 1) まとめて取る(1ファイルずつではなくマニフェストにする)
find /archive -type f -name '*.log*' | sort | xargs sha256sum > manifest-20260910T031500Z.sha256

# 2) 検証はワンライナー
sha256sum -c manifest-20260910T031500Z.sha256

# 3) 圧縮ログは「展開後の中身」のハッシュを取る
gzip -dc app.log.gz | sha256sum

ファイル名に空白が混ざりうる環境では、findxargs のヌル終端オプションを使ってください。そして「誰が」を足すには、マニフェストに分離署名を付けます(GnuPG など、組織で使っている鍵管理の仕組みで構いません)。ハッシュ値そのものには署名者がいないので、これは足さないと埋まらない欄です。

3本目が実務で見落とされます。gzip はヘッダに元ファイル名と mtime を書き込むので、まったく同じ内容を2回圧縮しても .gz のハッシュは一致しません。圧縮レベルや実装(gzip / pigz / zlib のバージョン)でも変わります。だから .gz のハッシュだけを台帳に載せると、あとで再圧縮した瞬間に「一致しない=改ざんの疑い」という無意味な騒ぎが起きます。台帳に載せるのは中身のハッシュです(gzip -n でファイル名と mtime を落とせば .gz 側も安定しますが、中身のハッシュを持つほうが確実です)。

置き場所の分け方は、コストの順に選べます。別ホストへ送る、追記のみ許可されたストレージへ書く、変更履歴の残るリポジトリへコミットする、印刷して封をする、第三者のタイムスタンプ(RFC 3161)を付ける。共通しているのは「ログを書き換えられる権限」と「ハッシュを書き換えられる権限」を別々の人・別々の場所に置くという一点です。

限界は2つ。

ひとつめ、どれも運用が要ること。1回やって満足し、半年後に誰も回していない、というのがいちばん多い失敗です。

ふたつめが本質的です。取るのが遅ければ、そのぶん何も言えない。 障害の3日後に取ったハッシュは、3日後以降の無改変しか示しません。つまりハッシュを取るべきタイミングは、1章の「再起動する前」と同じ場所にあります。保全と改ざん対策は、別々の作業ではなく同じ数十秒の中にある2つの動作です。


4つに共通していたもの

原則 証拠が失われる瞬間 直接の原因 汎用ツールでの対処 残る問題
保全 再起動・プロセス強制終了 未フラッシュのバッファと揮発領域 再起動前にダンプ・cpmv しない) 取れても読めない大きさ。深夜に手順を思い出せない
退避 ローテーションの狭間 copytruncate の隙間・rotate N の期限 logrotate -d で事前確認・prerotatecp I/Oと容量が2倍。cp で inode が変わり属性が根拠にならない
切り出し 報告書に貼った瞬間 座標・文脈幅・同一性の同時損失 grep -n -CawkNR を前置 幅を先に決める必要。断片だけでは検証できない
改ざん対策 ハッシュの置き場所を分けなかった時点 ハッシュはバイト一致しか言わない マニフェスト+分離保管+署名 取るのが遅い。.gz のハッシュは再圧縮で変わる

4つはまったく別の作業に見えます。担当者も、起きる時刻も違う。それでも右端の列が似た形をしているのは、4つとも「原本に触った瞬間に、触ったこと以外の何かが失われる」からです。

mv すれば元の場所が失われ、truncate すれば隙間が失われ、切り出せば座標が失われ、圧縮し直せばハッシュが失われる。だから原則は、突き詰めるとひとつしかありません——原本を原本のまま扱い、必要なものは原本を変えずに取り出す

必要な条件を3つに整理します。

  • 原本に書き込まない・分割しない・切り出さない — 読むために縮める必要がある道具は、この仕事に向きません。縮めた瞬間、3章の3つの損失が同時に起きます
  • 原本の座標を保ったまま絞り込む — 報告書に「12,034,541行目」と書ける形で最後まで残ること。段を重ねても番号が振り直されないこと
  • 同じファイルを何度も開き直すことが前提 — 保全したログは、1回読んで終わりではありません。報告書を書く日、レビューで差し戻された日、監査で聞かれた日。同じ数十GBを、何日にもわたって開き直します

冒頭の38分に戻ります。あれは再起動が消したというより、再起動の前の数十秒に何をするかが決まっていなかった時間です。決まってさえいれば、消えなかった。


使っている道具

私が開発している UwView(無料)は、巨大なテキストを開いた瞬間から表示・スクロール・検索できるビューアです。ファイル全体をメモリへ読み込まないので、RAM より大きいファイルでも開けます。索引はバックグラウンドで作られ、完成すると行番号が付きます。

上の3条件のうち、1つめは無料版がそのまま満たします。

  • 原本に書き込みません。 分割も切り出しもしないので、1章で cp したログを1本のまま・無改変のまま読めます。「読むために縮める」が不要になるので、3章の3つの損失が起きる場面そのものが減ります
  • ハイライトは行を消さずに色を付けます。 除外して見えなくするのではなく、色で仕分けるだけなので、あとから「除外した中に本命があった」が起きません
  • 処理はすべて手元のマシンで完結します。 対象ファイルを外部へ送りません。持ち出しの可否が問題になる保全ログでは、ここが前提条件になることがあります
  • 文字コードは開いたまま切り替えられます(UTF-8 / Shift-JIS(CP932) / EUC-JP / UTF-16 を自動判定)。iconv を挟むと第二のコピーが増えますが、それが起きません(第3回

2つめと3つめが UwView Pro の領分です。

  • 多段階検索(ドリルダウン): 絞り込んだ結果をさらに別の語で絞れます。タブに 語(件数) が並び、原本の行番号は最後の段まで保持されます。3章の「2段目から座標が消える」が起きないので、報告書に貼る引用に「原本の何行目」を付けられます。右クリックの履歴には、どの段のどの語が何行目で当たったかが順に出ます(多段階検索の記事
  • ±N は段ごとに独立: 絞り込む段は±1、実際に読む段は±20、というふうに、幅をあとから変えられます。3章の「-C 10 では足りないと分かってから grep をやり直す」が消えます(無料版は±1固定、可変の±N は Pro)
  • シーケンシャル検索: w1 → w2 → w3 がこの順に現れる箇所だけを拾います。1章の「正常終了を試した → 失敗した → 強制終了した」のような時系列の並びをそのまま条件にできます実装記事)。正直に書くと、各段は前段の位置から本文を走査するので、所要時間は全文検索と同程度かかります
  • 索引と圧縮を保存する: 一度開いたファイルは2回目以降行番号付きのまま瞬時に開き直せます(47.73GBのテキストで実測0.02〜0.07秒。特定環境での測定例で、環境により異なります)。上の3つめの条件——保全ログを何日も開き直す——にそのまま効きます
  • 約1/9で保管しつつ検索できる: 2章の「退避すると容量が2倍になる」に直接効きます。圧縮キャッシュ経由で検索できるので、zgrep のように毎回展開しながら走る必要がありません(第8回

4章のチェックサムに関係する機能もあります。Pro が作るサイドカー(.uwvz)は、圧縮ブロックごとに XxHash3 の表を持ち、展開のたびに検証します。オフセット表の健全性(単調増加・末尾が実ファイル長と一致)もオープン時に確認するので、保管中にビットが化けたり、コピーが途中で切れていたりすれば、気づかないまま読み進めることにはなりません。

ただし、これは改ざん検出ではありません。 XxHash3 は速度のための非暗号学的ハッシュで、故意の書き換えに耐える設計ではありません。4章で必要なのは sha256sum と分離保管であって、この検証はあくまで事故(ビット腐敗・転送ミス・切り詰め)に気づくためのものです。混同しないでください。

正直な限界

UwView はビューアです。SIEM でもフォレンジックスイートでもありません。

  • 1章のダンプ取得はしません。preserve.sh を書くのはあなたの仕事です
  • 2章の logrotate 設定も、退避の自動実行もしません
  • 4章のハッシュ計算も、署名も、保管の証跡(誰がいつ何を開いたか)の記録もしません。証拠管理の台帳を作る道具ではありません
  • 複数ログの自動突き合わせ、脅威情報との照合、アラート、報告書の自動生成——いずれもありません
  • ディスクイメージやメモリダンプは対象外です

もうひとつ、証拠を扱う人に関わる制約を正直に書いておきます。Pro のサイドカー(.uwvz)は、元ファイルの隣に新しいファイルとして作られます。 原本そのものは1バイトも変わりませんが、保全ディレクトリの中身を増やさない運用をしているなら、原本のコピーを別の作業領域へ置いてから開いてください(サイドカーは「元ファイルの長さ+最終更新時刻」を検証キーに持つので、原本が変わっていれば自動的に無効になります)。

この道具が担うのは、それら全部の手前の一歩です。保全した生ログを、そのまま・手元で・原本の座標を保ったまま、自分の目で読む。そこから先の相関分析やアラートが必要な規模なら、それは別の製品の仕事です。

参考までに、非破壊の差分編集(Edit Upgrade)は別ライセンスとして存在しますが、この記事の4項目はすべて読み取りだけの仕事です。ここで要るのは View 側です。

そして、ストレージを専有している巨大ログを圧縮して保管し、さらに高速に検索したいなら UwView Pro をどうぞ。永続索引・圧縮キャッシュ検索・約1/9保管で、開き直しも検索も一段速くなります(全OS対応・買い切り/月額プランあり)。

リンク

  • 第1回: 巨大ファイルに沈む4つの定番ツールと、その先: https://uvp.y42u.net/blog/uwview-ps01-huge-file-tool-limits/
  • 第3回: 文字コード4つの罠と切り分け手順: https://uvp.y42u.net/blog/uwview-ps03-japanese-encoding-traps/
  • 第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/
  • 第10回: 深夜2時の自分を助ける4つの仕込み: https://uvp.y42u.net/blog/uwview-ps10-oncall-night-preparation/
  • 第13回: 巨大データ検品の4技法: https://uvp.y42u.net/blog/uwview-ps13-huge-data-inspection/
  • 第14回: 攻撃の痕跡は生ログにある: https://uvp.y42u.net/blog/uwview-ps14-attack-traces-raw-logs/
  • 第15回: コマンドライン職人芸の限界線4本: https://uvp.y42u.net/blog/uwview-ps15-cli-craft-limits/
  • 絞り込んだ先からさらに絞り込む(多段階検索): https://uvp.y42u.net/blog/uvp-drilldown-search/
  • その順に現れる箇所だけを探す(シーケンシャル検索): https://uvp.y42u.net/blog/uvp-sequence-search/
  • 検索結果を独立ウィンドウに分離した理由: https://uvp.y42u.net/blog/uwview-filter-popup-jump-save-context/
  • アーカイブ×セッション復元の実務フロー: https://uvp.y42u.net/blog/uwview-archive-session-restore-workflow/
  • ソースコード(GitHub): https://github.com/amru195704/UwView

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


お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。実際のインシデント対応・証拠保全・監査対応は、各組織の規程および適用される法令・所管の指針に従ってください。本記事は法的な証拠能力について助言するものではありません。記事中の時刻・行番号・ファイル名はすべて説明のための例であり、特定の事案を指すものではありません。logrotatejournalctlsha256sumgzipgrep 等の挙動は、実装(GNU/BSD/busybox)・バージョン・ビルドオプション・ディストリビューションの既定設定により異なります。オプション名や既定値は必ず手元の man で確認してください。実測と明記した数値も特定の1環境での測定例であり、同じ結果を保証するものではありません。ディスクの種類・ファイルシステム・断片化・暗号化・ページキャッシュの状態・同時に動いている他のプロセスにより、結果は大きく変わります。誤記・不正確な情報がございましたら、コメント欄よりご指摘いただければ、確認のうえ修正いたします。

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