Fail2Banで攻撃IPを自動BANして
Slackに通知する
DjangoのセキュリティMiddlewareで攻撃を検知しても、同じIPから繰り返しアクセスされては意味がありません。
Fail2Banを使えば、攻撃IPをファイアウォールレベルで自動BANし、Slackに即座に通知できます。
Djangoに到達する前にブロックする、二段構えの防御を構築しましょう。
こんな人向けの記事です
- Fail2BanでBAN時にSlack通知を受け取りたい
- DjangoのセキュリティログをFail2Banで監視したい
- DoS攻撃をNginx + Fail2Banで防御したい
- 多層防御(Nginx → Fail2Ban → Django)の仕組みを理解したい
多層防御アーキテクチャ
Webサーバーへの攻撃は、以下の多層構造で防御します。
リクエスト送信
IPtablesでBAN
Rate Limit
攻撃検知→403
| 層 | 防御内容 | ブロック方法 |
|---|---|---|
| Fail2Ban | BAN済みIPの完全遮断 | iptablesでパケット破棄(Nginxにも到達しない) |
| Nginx | DoS攻撃(Rate Limit超過) | 503レスポンスを返却 |
| Django | XSS/SQLi/Traversal等 | 403レスポンス + security.logに記録 |
Djangoで攻撃を検知 → security.logに記録 → Fail2Banがログを監視 → IPをBAN → 以降はNginxにすら到達しないという流れです。
Slack通知スクリプトの作成
Fail2BanがIPをBAN/UNBANした際にSlackへ通知するシェルスクリプトを作成します。
#!/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.shFail2Banはsystemdサービスとして動作するため、ユーザーの環境変数を引き継ぎません。
/etc/environmentにトークンを書いておき、スクリプト内で読み込みます。
SLACK_BOT_TOKEN=xoxb-xxxxxxxxxxxx-xxxxxxxxxxxx-xxxxxxxxxxxxxxxxxxxxxxxxFail2Banアクションの設定
Fail2Banの「アクション」としてSlack通知スクリプトを登録します。
[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フィルタを作成します。
まず、ログのフォーマットを確認します。
2026-02-21 13:52:09,XSS Attack,10.0.0.1,Anonymous,GET,/,<script>,...フォーマット: 日時,攻撃種別,IPアドレス,ユーザー,メソッド,パス,...
[Definition]
# 日付抽出後の残り: ,XSS Attack,10.0.0.1,...
failregex = ^,.+?,<HOST>,
ignoreregex =
datepattern = ^%%Y-%%m-%%d %%H:%%M:%%SFail2Banは日付部分を先に抽出してから、残りの文字列にfailregexを適用します。
そのため正規表現は日付以降の,攻撃種別,<HOST>,にマッチするように書きます。
フィルタが正しく動作するかテストします。
sudo fail2ban-regex /path/to/security.log /etc/fail2ban/filter.d/django-security.confLines: 5 lines, 0 ignored, 5 matched, 0 missedJail設定
全てのjailにSlackアクションを追加した設定です。
[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 | 検知内容 | maxretry | bantime |
|---|---|---|---|
sshd | SSHブルートフォース | 3回 | 1時間 |
nginx-limit-req | Rate Limit超過(DoS) | 5回/60秒 | 1時間 |
nginx-botsearch | Botスキャン | 5回/60秒 | 24時間 |
django-security | XSS/SQLi/Traversal等 | 1回 | 24時間 |
攻撃と判定されたリクエストは1回でも即座にBANします。正当なユーザーがXSSパターンを含むリクエストを送ることは通常ありません。
sudo systemctl restart fail2ban
sudo fail2ban-client statusStatus
|- 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"期待される動作:
- Djangoが攻撃を検知 → 403応答 +
security.logに記録 - Fail2Banが
security.logを検知 → IPをファイアウォールでBAN #server-securityチャンネルにBAN通知が届く- 以降、同じIPからのリクエストはNginxにすら到達しない
sudo fail2ban-client status django-securityStatus 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攻撃検知の多層防御