監視・通知

Fail2Banで攻撃IPを自動BANしてSlackに通知する — サーバーレベルの防御

Fail2Ban Slack サーバー防御

Fail2Banで攻撃IPを自動BANして
Slackに通知する

DjangoのセキュリティMiddlewareで攻撃を検知しても、同じIPから繰り返しアクセスされては意味がありません。
Fail2Banを使えば、攻撃IPをファイアウォールレベルで自動BANし、Slackに即座に通知できます。
Djangoに到達する前にブロックする、二段構えの防御を構築しましょう。

こんな人向けの記事です

  • Fail2BanでBAN時にSlack通知を受け取りたい
  • DjangoのセキュリティログをFail2Banで監視したい
  • DoS攻撃をNginx + Fail2Banで防御したい
  • 多層防御(Nginx → Fail2Ban → Django)の仕組みを理解したい

多層防御アーキテクチャ

Webサーバーへの攻撃は、以下の多層構造で防御します。

攻撃者
リクエスト送信
Fail2Ban
IPtablesでBAN
Nginx
Rate Limit
Django
攻撃検知→403
防御内容ブロック方法
Fail2BanBAN済みIPの完全遮断iptablesでパケット破棄(Nginxにも到達しない)
NginxDoS攻撃(Rate Limit超過)503レスポンスを返却
DjangoXSS/SQLi/Traversal等403レスポンス + security.logに記録

Djangoで攻撃を検知 → security.logに記録 → Fail2Banがログを監視 → IPをBAN → 以降はNginxにすら到達しないという流れです。

Slack通知スクリプトの作成

Fail2BanがIPをBAN/UNBANした際にSlackへ通知するシェルスクリプトを作成します。

/usr/local/bin/slack_notify.sh
#!/bin/bash
# Fail2Ban Slack通知スクリプト

# /etc/environment から環境変数を読み込み
set -a
. /etc/environment 2>/dev/null
set +a

CHANNEL="#server-security"
ACTION="$1"  # ban or unban
IP="$2"
JAIL="$3"
HOSTNAME=$(hostname)

if [ -z "$SLACK_BOT_TOKEN" ]; then
    echo "[slack_notify] SLACK_BOT_TOKEN not set" >&2
    exit 0
fi

if [ "$ACTION" = "ban" ]; then
    COLOR="#dc3545"
    TITLE=":rotating_light: IP Banned"
else
    COLOR="#28a745"
    TITLE=":white_check_mark: IP Unbanned"
fi

PAYLOAD=$(cat <<EOF
{
    "channel": "${CHANNEL}",
    "attachments": [{
        "color": "${COLOR}",
        "title": "${TITLE}",
        "fields": [
            {"title": "IP", "value": "${IP}", "short": true},
            {"title": "Jail", "value": "${JAIL}", "short": true},
            {"title": "Server", "value": "${HOSTNAME}", "short": true}
        ]
    }]
}
EOF
)

curl -s -X POST "https://slack.com/api/chat.postMessage" \
    -H "Content-Type: application/json; charset=utf-8" \
    -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \
    -d "${PAYLOAD}"
sudo chmod +x /usr/local/bin/slack_notify.sh
なぜ /etc/environment を source するのか

Fail2Banはsystemdサービスとして動作するため、ユーザーの環境変数を引き継ぎません。
/etc/environmentにトークンを書いておき、スクリプト内で読み込みます。

/etc/environment
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxx

Fail2Banアクションの設定

Fail2Banの「アクション」としてSlack通知スクリプトを登録します。

/etc/fail2ban/action.d/slack.conf
[Definition]

actionstart =
actionstop =
actioncheck =

actionban = /usr/local/bin/slack_notify.sh ban <ip> <name>
actionunban = /usr/local/bin/slack_notify.sh unban <ip> <name>

<ip><name>はFail2Banが自動的に置換する変数です。

DjangoセキュリティログのFail2Banフィルタ

Djangoのsecurity.logを監視するFail2Banフィルタを作成します。

まず、ログのフォーマットを確認します。

security.log の例
2026-02-21 13:52:09,XSS Attack,10.0.0.1,Anonymous,GET,/,<script>,...

フォーマット: 日時,攻撃種別,IPアドレス,ユーザー,メソッド,パス,...

/etc/fail2ban/filter.d/django-security.conf
[Definition]
# 日付抽出後の残り: ,XSS Attack,10.0.0.1,...
failregex = ^,.+?,<HOST>,
ignoreregex =
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%S
正規表現のポイント

Fail2Banは日付部分を先に抽出してから、残りの文字列にfailregexを適用します。
そのため正規表現は日付以降の,攻撃種別,<HOST>,にマッチするように書きます。

フィルタが正しく動作するかテストします。

sudo fail2ban-regex /path/to/security.log /etc/fail2ban/filter.d/django-security.conf
Lines: 5 lines, 0 ignored, 5 matched, 0 missed

Jail設定

全てのjailにSlackアクションを追加した設定です。

/etc/fail2ban/jail.local
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3

[sshd]
enabled = true
port = 3715
filter = sshd
backend = systemd
maxretry = 3
action = %(action_)s
         slack

[nginx-limit-req]
enabled = true
port = http,https
filter = nginx-limit-req
logpath = /var/log/nginx/error.log
maxretry = 5
findtime = 60
bantime = 3600
action = %(action_)s
         slack

[django-security]
enabled = true
port = http,https
filter = django-security
logpath = /home/maru/homepage/log/security.log
maxretry = 1
findtime = 60
bantime = 86400
action = %(action_)s
         slack
Jail検知内容maxretrybantime
sshdSSHブルートフォース3回1時間
nginx-limit-reqRate Limit超過(DoS)5回/60秒1時間
nginx-botsearchBotスキャン5回/60秒24時間
django-securityXSS/SQLi/Traversal等1回24時間
django-security は maxretry=1

攻撃と判定されたリクエストは1回でも即座にBANします。正当なユーザーがXSSパターンを含むリクエストを送ることは通常ありません。

sudo systemctl restart fail2ban
sudo fail2ban-client status
Status
|- Number of jail:      6
`- Jail list:   django-security, nginx-bad-request, nginx-botsearch, nginx-http-auth, nginx-limit-req, sshd

動作確認

攻撃パターンを含むリクエストを送信してテストします。

テスト実行
# XSS攻撃のテスト
curl -s -o /dev/null -w "%{http_code}" \
  "https://example.com/?test=<script>alert(1)</script>"

# SQLインジェクションのテスト
curl -s -o /dev/null -w "%{http_code}" \
  "https://example.com/?id=1%20union%20select%20*%20from%20users"

期待される動作:

  1. Djangoが攻撃を検知 → 403応答 + security.logに記録
  2. Fail2Banがsecurity.logを検知 → IPをファイアウォールでBAN
  3. #server-securityチャンネルにBAN通知が届く
  4. 以降、同じIPからのリクエストはNginxにすら到達しない
BAN状況確認
sudo fail2ban-client status django-security
Status for the jail: django-security
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     5
|  `- File list:        /home/maru/homepage/log/security.log
`- Actions
   |- Currently banned: 5
   |- Total banned:     5
   `- Banned IP list:   10.3.0.1 10.3.0.2 10.3.0.3 10.3.0.4 10.3.0.5

まとめ

  • Fail2BanにSlack通知アクションを追加し、BAN/UNBAN時に即通知
  • DjangoのセキュリティログをFail2Banフィルタで監視
  • 攻撃IPは1回で即BAN(maxretry=1)、24時間遮断
  • BAN後はiptablesでパケット破棄 → Nginxにすら到達しない
  • SSH / Nginx Rate Limit / Botスキャン / Django攻撃検知の多層防御