アクセスログから不正アクセスの兆候を見つける7つの集計(nginx/Apache対応・コピペOK)

SecuritynginxApacheLinuxWordPress
元記事URLhttps://qiita.com/jiis-sasaki/items/7affa2e2ef22ebf93fa6
はてなブックマークhttps://b.hatena.ne.jp/ao41/20260913#bookmark-4792912550887628962
ブックマーク日時2026-09-13 23:04:48 JST(2026-09-13T14:04:48Z)
はてブ数92 users(記事取得時点:LGTM 33・コメントなし)
著者@jiis-sasaki(Qiita)/ 記事投稿日: 2026-09-10
タグあとで読む
📝 記事の概要
「うちのサイト、攻撃されていませんか?」——その答えはサーバのアクセスログの中にあります。生ログを tail で眺めても1日数万行から異常だけを拾うことはできません。本記事は SSHでサーバに入れるWeb担当者・サイト運営者・制作会社 向けに、アクセスログを 7本のコピペOKなコマンド に通して「見るべき数行」まで絞り込み、見つけた攻撃元IPを fail2banで自動遮断するまで をまとめた実践ガイドです。前提は nginx / Apache の combined 形式ログ(WordPress特有の見どころにも触れます)。

手順 0:ログに「本当のクライアントIP」が入っているか確認する

ここを確認しないと以降の集計は全部むだになります。Cloudflare・ロードバランサ・リバースプロキシ配下では、何もしなければ記録されるのはプロキシのIPです。上位が全部同じIPになり、そのまま fail2ban に食わせると自サイトへの入口ごと遮断してしまいます。

tail -1 /var/log/nginx/access.log

nginx の場合(realip モジュール)

set_real_ip_from 173.245.48.0/20;   # 信頼するプロキシのレンジを列挙
real_ip_header    CF-Connecting-IP;  # 既定は X-Real-IP
real_ip_recursive off;

Apache の場合(mod_remoteip)

ログ書式を %h から %a に変えるのがポイント(%a = 復元後のクライアントIP、%{c}a = 直前の接続元=プロキシ)。

RemoteIPHeader       CF-Connecting-IP
RemoteIPTrustedProxy 173.245.48.0/20

LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined_realip
CustomLog ${APACHE_LOG_DIR}/access.log combined_realip

反映後、自分の回線からサイトを開いて tail -1 に自分のIPが出れば準備完了です。

集計の共通部分(awk のフィールド位置)

combined 形式は nginx・Apache ともフィールド位置が同じなので、同じ awk がそのまま使えます。

203.0.113.10 - - [10/Sep/2026:07:12:33 +0900] "POST /wp-login.php HTTP/1.1" 200 1234 "-" "curl/8.5.0"
$1           $2$3 $4                    $5    $6    $7             $8       $9  $10  $11  $12
LOGS="/var/log/nginx/access.log /var/log/nginx/access.log.1 /var/log/nginx/access.log.*.gz"
zcat -f $LOGS | wc -l

Apache は /var/log/apache2/access.log*(RHEL系は /var/log/httpd/access_log*)に読み替え。

7つの集計コマンド

1送信元IPの上位 — 全体像の把握

zcat -f $LOGS | awk '{print $1}' | sort | uniq -c | sort -rn | head -20

2ログイン系エンドポイントへの POST — 最も分かりやすい兆候

zcat -f $LOGS | awk '$6=="\"POST" && $7 ~ /wp-login\.php|xmlrpc\.php/ {print $1}' \
  | sort | uniq -c | sort -rn | head -20

3404を量産しているIP — 脆弱性スキャナの動き

zcat -f $LOGS | awk '$9==404 {print $1}' | sort | uniq -c | sort -rn | head -20

4「何を探されているか」の一覧

zcat -f $LOGS | awk '$9==404 {print $7}' | sed 's/?.*//' \
  | sort | uniq -c | sort -rn | head -30

5そのうち2xxで返ってしまっているもの — ⚠️ 最重要

zcat -f $LOGS | awk '$9 ~ /^2/ && $7 ~ /\.env|\.git|\.bak|\.old|\.sql|\.zip|wp-config|phpinfo|adminer|phpmyadmin|\.DS_Store/ {print $7}' \
  | sed 's/?.*//' | sort | uniq -c | sort -rn | head -30
🚨 いちばん先に確認すべき集計 ここに1行でも出たら「攻撃されている」ではなく「すでに取られた」を意味します。バックアップや環境変数ファイルが実際に配信されたということなので、遮断より先に対象ファイルの削除と、そこに書かれていた認証情報の変更が必要。出力が空であることを確認するのがゴール。

6転送量の多い送信元 — 持ち出し検知

zcat -f $LOGS | awk '$9 ~ /^2/ {b[$1]+=$10} END {for (i in b) printf "%15.0f  %s\n", b[i], i}' \
  | sort -rn | head -20

7User-Agent の偏り

zcat -f $LOGS | awk -F'"' '{print $6}' | sort | uniq -c | sort -rn | head -20

兆候を「1日1回の差分」にする運用

手で叩くのは最初の1回だけにして、あとは日次で回し前日との差分だけを見るのが続けるコツ。スクリプト /usr/local/bin/access-log-digest.sh を cron で毎朝6:05に回し、前日分のダイジェスト差分をメールで受け取る構成が紹介されています。

見つけた相手を fail2ban で自動遮断する

集計2・3で常連が特定できたら自動遮断へ。fail2ban の既定値は maxretry=5 / findtime=10m / bantime=10m(1.1.0)=「10分以内に5回で10分BAN」。

💡 注意点 同梱の nginx系jail(nginx-http-auth / nginx-limit-req / nginx-botsearch)はいずれもエラーログを見るwp-login.php へのPOSTはアクセスログにしか出ないため、フィルタを1つ自作する必要があります。

自作フィルタとjailの例

# /etc/fail2ban/filter.d/wordpress-auth.conf
[Definition]
failregex = ^<HOST> .* "POST [^"]*/(wp-login\.php|xmlrpc\.php)
ignoreregex =
# /etc/fail2ban/jail.local
[wordpress-auth]
enabled  = true
filter   = wordpress-auth
port     = http,https
logpath  = /var/log/nginx/access.log
maxretry = 10        # 正規の打ち間違いを弾かない程度に緩める
findtime = 10m
bantime  = 1h
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24   # 自社オフィス・監視サービスのIPを必ず入れる

[recidive]           # 繰り返しBANされる常習者を長期BAN
enabled  = true

有効化前の確認コマンド

# 手元のログに何件マッチするか確認(BANはしない)
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/wordpress-auth.conf

fail2ban-client reload
fail2ban-client status wordpress-auth      # 現在のBAN一覧
fail2ban-client set wordpress-auth unbanip 203.0.113.10   # 誤爆の解除

誤爆させないための3ポイント

  1. ignoreip に自社IP・監視サービス・決済/予約システムのWebhook送信元を先に入れる。自分が締め出されるのが最も多い事故
  2. リバースプロキシ配下では手順0を必ず先に。復元していないとCDNのIPをBANして全訪問者が落ちる
  3. maxretry はいきなり絞らない。数日 bantime = 10m で様子を見てから伸ばす
⚠️ 遮断は更新の代わりにはなりません BANは反復を止めるだけで、脆弱性は残ったままです。

ログが残っていない、という落とし穴

まとめ

✅ まずやること ログは「攻撃されているか」だけでなく「設定の穴が実際に踏まれたか」まで答えてくれる唯一の記録です。まず集計5を1回流して、出力が空であることを確認するところから始めましょう。

関連記事(著者による)