失敗4,812回、成功1回。
同じIPからの認証ログです。攻撃なのか、パスワードを忘れた誰かなのか、まだ分かりません。
この1回の「成功」を、どうやって信じるか/疑うかを決めますか?
不正アクセスの疑いが上がった直後の数時間は、判断材料がログしかありません。しかも認証ログは、平時から数千万行たまっています。その中に、ごく少数の意味のある行が混ざっている——これが初動の難しさのすべてです。
この記事では、初動でたどる4つの手順を順に並べます。痕跡を見つける・ノイズを外す・IPの正体を確かめる・影響範囲を閉じる。どれも grep と awk で一応は進みます。そのうえで、その手がどこで止まるかまで踏み込みます。4手順とも、要求されている操作は同じ3つに集約されました。
前提として、本記事は「手元にログがある状態で、まず何を見るか」の話です。本格的なインシデントレスポンス(隔離・保全・報告)の手順は組織の規程に従ってください。
1. 認証ログ数千万行から、IPの出現パターンを拾う
状況
冒頭の 4,812 回を具体的にします。/var/log/auth.log 相当のログが数千万行。Failed password が数十万行、成功も数万行あり、どちらも「正常な運用の一部」として日常的に出ています。
見たいのは行そのものではありません。「どのIPが、どの時間帯に、どういう比率で失敗と成功を出しているか」という形です。
なぜ起きるか
認証ログは1行ずつが独立していて、攻撃の形は行と行の関係にしか現れないからです。
Failed password for admin from 203.0.113.44 という行は、それ単体では何の異常でもありません。深夜に同じIPから毎秒出ていれば異常ですし、営業時間に1日3回なら人間のタイプミスです。同じ文字列が、文脈しだいで正常にも異常にもなる。
しかも攻撃側は、この性質を知っています。1IPあたりの試行を数回に抑えて多数のIPに分散する、いわゆるパスワードスプレーは、1行ずつ見るかぎり完全に正常に見えます。「失敗が多いIP」を探す発想そのものが空振りします。
そしてもうひとつ。冒頭の「失敗4,812回、成功1回」で不気味なのは、回数ではなく並び方です。失敗が続いたあとに成功が来て、直後に鍵が追加され、その後に別ホストへの接続が出る。この 失敗の連続 → 成功 → 鍵追加 → 横移動 という順序が揃ったときにはじめて、それは侵入の形になります。同じ4語がバラバラの順で出ているなら、たぶん何でもありません。探したいのは語の集合ではなく、語の並びです。
汎用ツールでの対処と限界
まず集計から入ります。
grep 'Failed password' auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head -20
grep 'Accepted publickey\|Accepted password' auth.log | awk '{print $(NF-3)}' | sort | uniq -c | sort -rn | head
# 逆向き:1IPあたりの試行が少なく、対象ユーザー名だけが多いパターン
grep 'Failed password' auth.log | awk '{print $(NF-5)}' | sort | uniq -c | sort -rn | head
3本目が効きます。IPで数えると分散攻撃は消えますが、狙われたユーザー名で数えると浮きます。集計軸を変えるだけで見え方が変わるのは、この領域の定石です。
当たりが付いたら、次は前後を読みます。
grep -n -C 5 '203\.0\.113\.44' auth.log | less # ヒットの前後5行を文脈ごと
grep -C 5 '203\.0\.113\.44' auth.log | grep -n 'Accepted\|new key\|sudo' # その文脈の中だけを再検索
grep -C N(前後N行)は、初動でいちばん使う道具です。1行では判断できないものが、前後5行あるとたいてい判断できます。そして2本目のように、その文脈の中だけをさらに別の語で絞る——これが実務のパイプです。
限界は3つ。ひとつめ、Nをいくつにすればいいか事前には分からないことです。±3では足りず、±50では読めない。適切なNは対象ごとに違い、同じ調査の中でも変わります(絞る段では狭く、読む段では広く)。ところが grep -C はNを変えるたびに数GBの原本を頭から読み直します。
ふたつめ、パイプで絞った瞬間に原本の行番号が消えることです。2本目の grep -n が数えているのは「流れてきた文脈ブロックの中での番号」であって、auth.log の行番号ではありません。「じゃあその行に戻ろう」ができない。しかも絞り込みは語を足したり外したりの繰り返しで、そのたびにパイプを組み直し、原本を読み直すことになります。
みっつめ、grep は順序を表現できないことです。前節の 失敗の連続 → 成功 → 鍵追加 を探そうとしても、grep -E 'Failed|Accepted|new key' は3語のどれかを含む行を返すだけで、並びは見ません。awk で状態機械を書けば書けますが、当たりを付けるたびに使い捨てのスクリプトを書くのは初動の速度に合いません。結局この判定は目でやることになり、第2回の「一覧と現場の往復」が何十回も繰り返されます。
2. 暴走したクローラーを、攻撃と切り分ける
状況
Webサーバの負荷が跳ねた夜。アクセスログでは、特定のパスへのリクエストが桁違いに増えています。攻撃か、それとも設定を誤ったクローラーが同じページを踏み続けているだけか。判断を誤れば、悪意のないクローラーを遮断してインデックスを落とすか、本物の探索行為を「botでしょう」と見送ることになります。
なぜ起きるか
bot と攻撃は、アクセスログ上でほとんど同じ形をしているからです。
どちらも、同一IP(またはIP帯)から高頻度で、機械的な間隔で、人間が踏まない順序でURLをたどります。違いは意図であって、意図はログに書かれていません。
User-Agent は手がかりですが、自己申告なので信用度に上下があります。Googlebot を名乗るリクエストが本当にGoogleから来ているとは限らず、確認には逆引き(PTR)と正引きの往復が要ります。
さらに厄介なのは、両方が同時に起きている場合です。クローラーの暴走で負荷が上がっている裏で、そのノイズに紛れて別のIPが探索している。前者だけ見て「原因判明」と閉じると、後者を見落とします。
汎用ツールでの対処と限界
分離は集計で当たりを付けます。
awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -rn | head -20 # UAの分布
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20 # IPの分布
# 名乗りが本物か(1件ずつ確認する)
dig +short -x 66.249.66.1 | xargs -r dig +short
# UAで除外したうえで、残りのIPを数え直す
grep -v -E 'Googlebot|bingbot|Applebot' access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head
最後の「既知botを除いてから数え直す」は、実務でいちばん効く一手です。ノイズを減らすと、隠れていた第2の山が見えます。そして実際の切り分けは、これを何段も重ねる作業になります。UAで除く → 残りをIP帯で除く → 残りをパスで絞る → 残りを時間帯で絞る。絞り込みは1回では終わらず、段を積んでいくものです。
限界は3つ。ひとつめ、この積み重ねが一本のパイプでしか表現できないことです。4段目まで積んで「3段目が厳しすぎたかも」と思ったら、書き直して最初から流し直し。しかも段ごとの件数が残らないので、どの段でどれだけ落ちたのかが分かりません。「9.5万件 → 184件 → 54件」のように減り方が見えれば効いた条件が即座に分かるのですが、パイプの途中経過は流れて消えます。
ふたつめ、除外が破壊的なことです。grep -v で作った中間ファイルは、除外した行を持っていません。「やはりあのクローラーの時間帯だけ戻したい」と思っても原本から作り直しで、除外条件は初動で何度も変わります(UAを1つ足す、IP帯を1つ外す)。そのたびに数GBを読み直すことになります。
みっつめ、除外と全体を同時に見たい場面が必ず来ます。「bot を除いた残り」と「bot 込みの時系列」を並べて山の位置が一致するかを見る。grep -v はどちらか一方しか手元に残しません。
3. 「そのIP、社内からですか」— 払い出しログと突き合わせる
状況
社内ネットワークからのアクセスとして 10.20.30.44 が記録されています。上司に「誰の端末ですか」と聞かれます。
プライベートIPは、そのアドレス単体では誰も指しません。DHCPで払い出されている以上、8月14日の 10.20.30.44 と 8月20日の 10.20.30.44 は別人の可能性があります。
なぜ起きるか
「IPと人の対応」が時間とともに変わり、その履歴は別のログに書かれているからです。
必要な情報が2か所に分かれています。アクセスログには「いつ・どのIPが・何をしたか」があり、DHCPサーバのログには「いつ・どのIPを・どのMACアドレスに貸したか」がある。片方だけでは答えが出ません。しかもサーバ間の時刻が数秒ずれているだけで、リース境界の帰属は反転します。
汎用ツールでの対処と限界
素朴にやるなら、両方から抜いて目で並べます。
grep '10\.20\.30\.44' access.log | head -50
grep '10\.20\.30\.44' dhcpd.log | grep -E 'DHCPACK|DHCPRELEASE'
# 時刻順にマージして1本の物語にする(列位置は環境ごとに調整)
sort -m -k1,2 <(grep '10\.20\.30\.44' access.log) <(grep '10\.20\.30\.44' dhcpd.log) | less
3本目のマージは強力です。1本の時系列になれば、リースの切り替わりとアクセスの前後関係が目で追えます。
そしてここでも、判定したいのは順序です。DHCPACK(払い出し) → 問題のアクセス → DHCPRELEASE(返却)。この順なら、そのアクセスはそのリース期間の中で起きた。順序が崩れていれば——アクセスが DHCPACK より前なら——それは前の借り主の行為です。1分の差で結論が反転する、というのはこの意味です。
限界は3つ。ひとつめ、時刻フォーマットが揃っていないことです。syslog 形式(Aug 31 04:12:07)とISO 8601 が混ざると sort は素直に並べてくれず、前処理が要ります。前処理をすれば原本の行番号は消えるので、第5回と同じ「正規化した瞬間に戻れなくなる」問題が出ます。
ふたつめ、DHCPログは往々にして別のマシンにあり、別の文字コードで書かれていることです。Windows系機器のログが Shift_JIS のまま、というのは日本の現場では珍しくありません(切り分け手順は第3回)。
みっつめ、マージは「1本にする」操作なので、どちらの行がどちらのファイル由来か分からなくなることです。本当にやりたいのは、2つを左右に置いて同じ時刻帯を同時に追うことでした。
4. 退職者アカウントの最終アクティビティを、網羅的に洗う
状況
侵入の疑いがあると、必ず出る宿題があります。「無効化し忘れたアカウントはないか」。対象は退職者・異動者・テスト用・業務委託で、人数は数十人、ログは数か月ぶん。ここで求められるのは面白い発見ではなく、漏れがないことです。
なぜ起きるか
「何も出なかった」を証明するには、全部を見るしかないからです。
通常の障害調査は、答えが1つ見つかれば終わります。棚卸しは逆で、見つからないことを確認するために全件を通す必要がある。1人でも見落とせば、その1人が本命だったかもしれない。
そして候補リストは、たいていログの外にあります。人事の名簿、退職日の一覧、Excelで渡されたアカウントIDの列。ログ側は jsmith としか書いておらず、名簿は「John Smith(2026-05-31退職)」。この対応付けは人間がやることになります。
汎用ツールでの対処と限界
リストがあるなら、ループで回せます。
# 退職者IDの一覧から、最終アクティビティだけを抜く
while read -r u; do
last=$(grep -h "user=$u" auth.log* | tail -1)
printf '%s\t%s\n' "$u" "${last:-(記録なし)}"
done < leavers.txt
# 退職日以降に活動があるものだけを見る
grep -F -f leavers.txt auth.log | grep 'Accepted' | awk '{print $NF, $0}' | sort | head -50
grep -F -f リストファイル は、この用途の主力です。数十個のIDを一度に、しかも固定文字列として(正規表現の誤爆なしで)探せます。
限界は2つ。ひとつめ、出てきた1行だけでは判断できないことです。「2026-06-12 に jsmith のログイン成功」が不正なのか、退職手続き中の正当な作業なのか、同名の別アカウントなのかは、その行の前後を読まないと分かりません。ここでも grep -C を足したくなりますが、数十人ぶんのループで前後を出すと出力が数千行になり、今度はそれを読む作業が発生します。欲しいのは「ヒット一覧」と「そのヒットの周辺」を行き来しながら見ることであって、両方を混ぜた1本のテキストではありません。
ふたつめ、この作業が数日にまたがることです。翌日また同じログを開き、どこまで確認したかを思い出し、同じ検索条件を組み直す。第5回の「調査の状態がツールの外にある」問題が、ここでは人数ぶん掛け算になって効いてきます。
4手順に共通していたもの
| 手順 | 現れ方 | 欲しかった操作 | 止まる場所 |
|---|---|---|---|
| IPの出現パターンを拾う | 痕跡は語の集合でなく語の並びに出る | 前後N行 + 順序で探す | grep -C はNを変えるたび全読み。grep は順序を書けない |
| botと攻撃を切り分ける | 絞り込みは段を積む作業になる | 絞り込みの重ね掛け | パイプは途中経過が残らず、条件を変えると最初から |
| IPの正体を突き合わせる | 判定は ACK → アクセス → RELEASE の順序 |
前後N行 + 順序で探す | 2本を並べて同時に追えない |
| 退職者アカウントを洗う | 1行では判断できず、全件通しが要る | ヒット一覧と周辺の行き来 | 数十人ぶん原本に戻り、それが数日続く |
4つとも、詰まっているのは「解析の手法」ではありません。表の3列目を見ると、要求されている操作はたった3つに収まります。
- 前後N行(±N)を、Nを変えながら見る — 1行では何も決まらない。決まるのは文脈が付いてからで、必要な文脈の幅は毎回違う。
- 絞り込みを重ね、段ごとの件数を見る — 1回の検索で終わることはない。「9.5万件 → 184件 → 54件」のように減り方が見えると、どの条件が効いたかが分かる。
- 順序で探す — 失敗の連続のあとの成功、払い出しのあとのアクセス。攻撃の痕跡は語の集合ではなく、語の並びに現れる。
この3つは、小さいファイルなら誰でも当たり前にやっていることです。それが数GBになった途端、grep -C はNを変えるだけで全読みを要求し、パイプは途中経過を捨て、順序の判定は使い捨てスクリプトか目視になる。操作が難しいのではなく、大きさのせいで割に合わなくなる。
セキュリティの初動では、これが特に効きます。仮説が外れる前提で次々と切り口を変えるからです。IPで数えて外れ、ユーザー名で数えて当たり、前後を広げ、botを除いて数え直す。1回の待ちが数十秒でも、初動の数時間では積み上がります。積み上がった結果、「もう1つ試したい仮説」が試されないまま初動が終わります。
そしてこれは、連載で繰り返し出てきた形です。第1回の「開くのに全読みを要求する」、第2回の「一覧と現場の往復」、第4回の「小さく置くと読めなくなる」。認証ログもアクセスログも、詰まり方は障害調査と同じでした。違うのは、間違えたときの代償だけです。
使っている道具
私が開発している UwView(無料)は、この「大きいと往復が割に合わなくなる」前提を外すために作ったビューアです。数千万行の認証ログでも開いた瞬間から全体を表示・スクロール・検索でき、索引はバックグラウンドで作られて、完成すると行番号が付きます。ハイライトは行を捨てずに色を付けるので、第2章の「除外したいが消したくない」もそのままです。原本には書き込みません(フォレンジックの第一原則)。
そして、上に並べた3つの操作——±N・絞り込みの重ね掛け・順序で探す——をそのまま持っているのが UwView Pro です。
- 多段階検索(ドリルダウン): 検索結果を、さらに別の語で絞り込む。タブに
語(件数)が並ぶので、第2章の「UAで除く → IP帯で除く → パスで絞る」が段ごとの件数を見ながら進みます。2段目以降は前段の±N範囲だけが対象なので待ちがなく、条件を変えたいときはその段のタブに戻るだけ。パイプの組み直しは要りません。→ 多段階検索の記事 - シーケンシャル検索:
w1 → w2 → w3がこの順で現れる箇所だけを拾います。第1章の失敗の連続 → 成功 → 鍵追加、第3章のDHCPACK → アクセス → DHCPRELEASEが、そのまま検索条件になります。右クリックの「履歴」で成立した順序の経路そのもの(何行目の何 → 何行目の何)が見え、どの行へもジャンプできます。ただし各段は前段の位置を開始点に本文を検索するため、所要時間は全文検索とほぼ同じです(ドリルダウンの「2段目以降は即時」とはここが違います)。→ シーケンシャル検索の記事 - ±N は段ごとに独立: 絞る段は±1で締めて、最後に読む段だけ±10に広げる。Nを変えても原本は読み直しません。
索引と圧縮を保存するので、2回目以降は行番号付きで瞬時に開けます。圧縮キャッシュ経由の検索と約1/9保管も付くので、保管義務のある古いログを抱えたままでもディスクが楽になります(全OS対応・買い切り/月額プランあり)。なお無料版で使えるのは1段検索・±1文脈・結果の保存までで、多段階検索・シーケンシャル検索・可変の±N は Pro の機能です。
正直に書いておくと、UwView はビューアであって、SIEM でも相関分析エンジンでもありません。複数ログの自動相関やアラートが必要な規模なら、そちらの製品が正解です。UwView が担うのは、その前段——取り込む前の生ログを、そのままの形で人間が見るところです。
リンク
- 第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/
- 第3回: 文字コード4つの罠と切り分け手順: https://uvp.y42u.net/blog/uwview-ps03-japanese-encoding-traps/
- 第4回: 消す・残す・圧縮するの判断基準: https://uvp.y42u.net/blog/uwview-ps04-log-retention-decision/
- 第5回: 開発ログとの正しい付き合い方4選: https://uvp.y42u.net/blog/uwview-ps05-debug-log-practices/
- 多段階検索(ドリルダウン)— 9万件を2回の絞り込みで54件に: https://uvp.y42u.net/blog/uvp-drilldown-search/
- シーケンシャル検索 — 「この順で現れる場所」だけを探す: https://uvp.y42u.net/blog/uvp-sequence-search/
- nginxアクセスログの5xxを追う: https://uvp.y42u.net/blog/uwview-access-log-5xx-workflow/
- 色分けハイライトの使い方: https://uvp.y42u.net/blog/uwview-v11-color-highlighter/
- ソースコード(GitHub): https://github.com/amru195704/UwView
開発者より: アプリ・Kindle本・オープンソースの一覧は GitHub: amru195704 にまとめています。
お願い
本記事の情報は参考目的で掲載しており、正確性・完全性を保証するものではありません。実際のインシデント対応は、所属組織の規程と、必要に応じて専門機関の指示に従ってください。ログの形式・フィールド位置・時刻表記は、OS・ミドルウェア・設定により大きく異なります。コマンド例は環境(GNU/BSD、シェル、awkの実装、ログのフィールド構成等)により調整が必要です。IPアドレスはすべて文書用の例示アドレスです。誤記・不正確な情報がございましたら、コメント欄よりご指摘いただければ、確認のうえ修正いたします。

