アクセスログの攻撃痕跡を読む — スキャン・ブルートフォース・脆弱性試行を見抜く演習
合成のWebアクセスログを題材に、スキャナ特有の連続アクセスや不自然なUser-Agent、ログインへの短時間POST集中、SQLインジェクション・パストラバーサルの試行痕跡を読み解き、正常アクセスと見分ける判断力を鍛える演習です。
この記事の目次 5項目から選ぶ
Webサーバーのアクセスログは、外部からの攻撃が最初に形となって現れる場所のひとつです。脆弱性スキャナによる偵察、ログイン画面へのブルートフォース攻撃、SQLインジェクションの試行は、いずれもアクセスログに特徴的な痕跡を残します。逆に言えば、痕跡の読み方を知っていれば、大掛かりな製品がなくても「今、自社のサイトが狙われている」という事実に気づくことができます。
この演習では、合成のApache形式のアクセスログを読みながら、「どの行が攻撃らしいか」「正常なアクセスとどう見分けるか」「見つけた後の初動は何か」を実践的に確認します。SOCアナリストの監視業務はもちろん、情シスが公開サーバーを点検する場面、開発者が自分の書いたアプリへの攻撃を把握する場面でもそのまま使える読み方です。
本記事は教育目的の演習であり、登場するデータ(IPアドレス・ドメイン・User-Agent・ログの各行)はすべて合成の例です。IPアドレスには例示用の TEST-NET(192.0.2.0/24、198.51.100.0/24、203.0.113.0/24)と社内プライベートアドレス(10.0.0.0/8)のみを使用しています。
状況設定
あなたは従業員300名の製造業の会社で情シスを担当しています。社外に公開している製品紹介サイト(https://example.jp/)の保守を兼任しており、ある朝、サイトのレスポンスがいつもより遅いという社内報告を受けました。
Webサーバー(Apache)はクラウド上のVPSで稼働しており、まずはアクセスログを確認することにしました。以下は当日 09:10〜09:15 のアクセスログ(Combined Log Format)からの抜粋です。普段のアクセスは社内からの閲覧と検索エンジン経由の訪問が中心で、1分間に数件程度しかありません。
証跡: アクセスログ
| 10.0.1.24 - - [20/Jul/2026:09:10:02 +0900] "GET / HTTP/1.1" 200 5321 "https://example.jp/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" |
| 10.0.1.24 - - [20/Jul/2026:09:10:03 +0900] "GET /assets/style.css HTTP/1.1" 200 18234 "https://example.jp/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" |
| 203.0.113.50 - - [20/Jul/2026:09:12:01 +0900] "GET / HTTP/1.1" 200 5321 "-" "sqlmap/1.7.2#stable (http://sqlmap.org)" |
| 203.0.113.50 - - [20/Jul/2026:09:12:02 +0900] "GET /.git/HEAD HTTP/1.1" 404 512 "-" "sqlmap/1.7.2#stable (http://sqlmap.org)" |
| 203.0.113.50 - - [20/Jul/2026:09:12:03 +0900] "GET /.env HTTP/1.1" 404 512 "-" "sqlmap/1.7.2#stable (http://sqlmap.org)" |
| 203.0.113.50 - - [20/Jul/2026:09:12:04 +0900] "GET /wp-login.php HTTP/1.1" 404 512 "-" "sqlmap/1.7.2#stable (http://sqlmap.org)" |
| 203.0.113.50 - - [20/Jul/2026:09:12:06 +0900] "GET /products/detail?id=10%27 HTTP/1.1" 500 603 "-" "sqlmap/1.7.2#stable (http://sqlmap.org)" |
| 203.0.113.50 - - [20/Jul/2026:09:12:09 +0900] "GET /download?file=..%2f..%2fsecret%2fconfig.txt HTTP/1.1" 400 431 "-" "sqlmap/1.7.2#stable (http://sqlmap.org)" |
| 198.51.100.77 - - [20/Jul/2026:09:13:41 +0900] "POST /login HTTP/1.1" 401 288 "-" "python-requests/2.31.0" |
| 198.51.100.77 - - [20/Jul/2026:09:13:43 +0900] "POST /login HTTP/1.1" 401 288 "-" "python-requests/2.31.0" |
| 198.51.100.77 - - [20/Jul/2026:09:13:45 +0900] "POST /login HTTP/1.1" 401 288 "-" "python-requests/2.31.0" |
| 198.51.100.77 - - [20/Jul/2026:09:13:47 +0900] "POST /login HTTP/1.1" 401 288 "-" "python-requests/2.31.0" |
| 198.51.100.77 - - [20/Jul/2026:09:13:49 +0900] "POST /login HTTP/1.1" 401 288 "-" "python-requests/2.31.0" |
| 10.0.1.31 - - [20/Jul/2026:09:14:05 +0900] "GET /news/2026-07 HTTP/1.1" 200 8421 "https://example.jp/news/" "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1" |
| 10.0.1.24 - - [20/Jul/2026:09:14:30 +0900] "POST /login HTTP/1.1" 302 0 "https://example.jp/login" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" |
それでは、このログを順に読み解いていきましょう。
Q1 3〜8行目の 203.0.113.50 からのアクセスは何をしていると考えられるか?
これは自動化ツール(スキャナ)による偵察と脆弱性の試行です。根拠は3つあります。
- User-Agent がツール名そのもの:
sqlmap/1.7.2#stableは、SQLインジェクションの検査・悪用を自動化する有名なオープンソースツールのデフォルトUser-Agentです。正規のブラウザや検索エンジンクローラーがこの文字列を名乗ることはありません。 - 存在しないパスへの404連発:
/.git/HEAD、/.env、/wp-login.phpはいずれも「Gitリポジトリの露出」「環境変数ファイルの露出」「WordPressのログイン画面」を探す典型的な偵察パスです。このサイトはWordPressではないため404が返っていますが、それ自体が「当たりを探している」証拠です。 - 1〜3秒間隔の機械的な連続アクセス:09:12:01 から 09:12:09 のわずか8秒間に6リクエスト。人間のブラウジングではこの間隔で異なるパスを連続して叩くことはまずありません。
MITRE ATT&CK の分類では、公開アプリケーションへのこの種の試行は T1190(Exploit Public-Facing Application) に対応します。
Q2 9〜13行目の 198.51.100.77 からの POST /login の連続は何か?
ログインに対するブルートフォース攻撃(パスワード総当たり)の試行です。MITRE ATT&CK の T1110(Brute Force) に該当します。
- 同一IPから2秒間隔で /login にPOSTが集中:09:13:41〜09:13:49 の8秒間に5回。正常なユーザーがログインを試みる頻度ではありません。
- User-Agent が
python-requests:PythonのHTTPライブラリのデフォルト文字列であり、スクリプトによる自動実行を示唆します。Referer も空(-)で、ログイン画面を経由せずいきなりPOSTしている点も機械的です。 - 全件 401(認証失敗)でレスポンスサイズが同一:パスワードを変えながら繰り返し試行し、いずれも失敗している様子がうかがえます。
なお、同一パスワードを多数のアカウントに試す「パスワードスプレー」や、漏洩済みのID・パスワードの組を流用するクレデンシャルスタッフィングも、ログ上は似た形で現れます。対象アカウントの数や成功の有無で区別します。
Q3 9〜13行目はすべて 401(認証失敗)だった。「失敗しているのだから被害なし」と結論づけてよいか?
アクセスログだけでは結論づけられません。 以下の確認が必要です。
- 抜粋の時間帯の外に成功がないか:この抜粋は09:13台だけです。09:13:49 以降に同じIPから 200 や 302 のPOST成功が続いていないか、ログ全体で確認する必要があります。
- 別IPからの成功と照合する:攻撃者はIPを変えて試行を続けることがあります。成功したログイン(302や200)が、普段と異なる送信元から発生していないかを確認します。
- アプリケーション側の認証ログを見る:Webサーバーのアクセスログには「どのアカウントへの試行か」は記録されません。監査ログや認証基盤のサインインログで、標的になったアカウントとロックアウトの有無を確認します。
「試行の失敗」と「被害なし」は別の話です。試行が観測された時点で、パスワードポリシーやMFAの適用状況の確認という初動が始まります。
Q4 15行目の 10.0.1.24 からの POST /login(ステータス302)は攻撃か?
これは正常なログインと判断できます。9〜13行目と同じ「POST /login」ですが、見るべき点がすべて異なります。
- 302(リダイレクト):ログイン成功後に別ページへ遷移したことを示します。401の連続とは対照的です。
- Referer が
/login:ログイン画面を表示してからフォームを送信した、ブラウザの自然な動作です。 - ブラウザらしいUser-Agent:Chromeを名乗る完全な文字列で、同一IPの1〜2行目の閲覧アクセスと一貫しています。社内アドレス(10.0.1.24)からの、朝の通常業務の一コマと読めます。
このように「同じURLへのPOST」でも、ステータス・Referer・User-Agent・前後の行の流れの4点で良性か悪性かが大きく分かれます。
観察ポイントの解説
アクセスログの基本フォーマットを押さえる
今回のログはApacheの Combined Log Format で、Nginxでもほぼ同じ形式がデフォルトで使われます(Apache公式ドキュメント、NGINX ngx_http_log_module)。1行は左から次の意味を持ちます。
- クライアントIP:送信元。社内か外部か、普段見るIPかをまず見る
- 日時:間隔の計測に使う。秒単位の連続は自動化の兆候
- リクエスト行(
"GET /path HTTP/1.1"):メソッドとパス。POSTの向き先やURL中の不自然な文字に注目 - ステータスコード:200(成功)、302(リダイレクト)、401/403(認証・権限エラー)、404(存在しない)、500(サーバーエラー)。分布の異常が重要
- レスポンスサイズ:同一サイズの連続は「同じ失敗画面が返っている」ことを示す
- Referer:どこから来たか。空(
-)は直叩きの可能性 - User-Agent:ブラウザ・ツールの名乗り。ただし偽装可能
スキャナ特有の痕跡
- 機械的な間隔:1〜数秒間隔で異なるパスへ連続アクセス。人間はページを読む時間があるため、通常はもっとばらつきます
- 404の連発:
/.git/、/.env、/wp-admin/、/phpmyadmin/など「露出していると危険なファイル・有名CMSの管理画面」を順に試すパターンは偵察の典型です - ツール名入りのUser-Agent:sqlmap、Nikto、Nmap、acunetix などの文字列はそのままIoC(侵害指標)になります。ただし、これは「名乗っている」場合だけの話で、後述の通り偽装は容易です
ブルートフォースの痕跡
- 同一IPからの /login への短時間POST集中:今回の2秒間隔×5回は分かりやすい例です。実際には1分間に数十件、数時間にわたる低速型(ロー&スロー)もあります
- 401の連続と同一レスポンスサイズ:失敗画面が繰り返し返っている形跡です
- スクリプト系User-Agent:
python-requests、curl、Go-http-clientなど。Refererが空であることも多い
SQLインジェクション・パストラバーサルの試行痕跡
URLのパラメータ部分に、データベースやファイルシステムへの操作を意図した特殊文字が混入しているかを見ます。
- SQLインジェクション系:シングルクォート(URLエンコードで
%27)や--、UNION や SELECT を含む試行です。今回の7行目のid=10%27は、数値しか入らないはずのIDにシングルクォートを混入させてエラーを誘発する、典型的な第一歩です。直後に 500エラーが増える のは、試行がアプリケーションまで届いてSQL文を壊している兆候であり、WAFや修正の必要性が高い状態です - パストラバーサル系:
../(URLエンコードで%2e%2e%2fなど)を使い、公開ディレクトリの外のファイルを読もうとする試行です。8行目のfile=..%2f..%2fsecret%2fconfig.txtは「上の階層に遡って設定ファイルを取れないか」を試しています。400で弾けていますが、200が返るパターンがあれば情報漏洩の可能性があり即調査です
これらは詳細な挙動を再現する必要はなく、「パラメータに混入すべきでない文字が来ているか」「その結果ステータスがどうなったか」の2点を追えば検知につながります。
正常アクセスとの見分け方
攻撃の痕跡を探すだけでなく、正常系の特徴を知ることで誤検知を減らせます。
- Refererの遷移が自然:トップ → CSS/画像 → 詳細ページ、のようにリンクを辿った跡がある
- User-Agentが一貫:同一IP内でブラウザ文字列が揃っている。OSやバージョンが不自然に混在しない
- 間隔が人間的:数秒〜数分のばらつきがある
- 静的リソースも取得している:HTMLだけでなくCSS・JS・画像をセットで取るのはブラウザの特徴
一方で注意点もあります。User-Agentは偽装できるため、「Chromeを名乗っているから安全」ではありません。検索エンジンの公式クローラーかどうかは、送信元IPの逆引きで検証するのが確実です(Googleは公式の検証手順を公開しています)。User-Agentはあくまで手がかりのひとつとして扱いましょう。
判定と初動アクション
今回のログ全体の判定は次の通りです。
攻撃の試行を確認(侵入成功の形跡は抜粋内にはなし・要継続調査)203.0.113.50 からのスキャンと脆弱性試行、198.51.100.77 からのブルートフォースという、2系統の攻撃の試行が同時刻帯に観測されています。いずれも抜粋の範囲では失敗していますが、これは「攻撃を受けている状態が継続している」ことを意味します。
初動でやること
- ログの保全:当該時間帯および前後数日分のアクセスログを、改変されない場所にコピーして保全する(フォレンジックの基本)
- 範囲の拡大確認:同じIPからの過去のアクセスがないか、成功を示す200/302が後続にないか、ログ全体で確認する
- 認証ログとの突合:どのアカウントが狙われたか、ロックアウトやMFAで防げたかを認証基盤の監査ログで確認する
- 防御の確認・強化:WAFのルール適用状況、ログインのレート制限・アカウントロック、MFAの適用状況を確認する
- 継続監視への登録:観測したIPやUser-Agent文字列をIoCとしてSIEMや監視ルールに登録し、再訪を検知できるようにする
初動でやらないこと
- ログを直接編集・削除しない:証跡が失われ、後の調査や報告の根拠が崩れます
- 攻撃元IPへアクセスしに行かない:反撃・確認のつもりでも、相手にこちらの情報を与えるだけです
- 401や404だけを見て「被害なし」と報告しない:Q3で見た通り、失敗の観測は被害不存在の証明ではありません
- 闇雲にIPを遮断して終わりにしない:攻撃者はIPを変えます。遮断は有効ですが、根本原因(脆弱なエンドポイント、弱い認証)の確認を省略しないこと
参考情報
- Apache HTTP Server ドキュメント: ログファイル — Combined Log Formatの各フィールドの意味とカスタマイズ方法
- NGINX 公式ドキュメント: ngx_http_log_module — Nginxのログフォーマット変数とaccess_logディレクティブ
- MITRE ATT&CK: T1110 Brute Force — パスワード推測・パスワードスプレー・クレデンシャルスタッフィングの分類
- MITRE ATT&CK: T1190 Exploit Public-Facing Application — 公開アプリケーションへの攻撃の位置づけと緩和策