| name | detecting-port-scanning-with-fail2ban |
| description | 使用自定义过滤器和动作配置 Fail2ban,检测端口扫描活动、SSH 暴力破解尝试和网络侦察,自动封禁攻击 IP 并向安全团队告警可疑的网络探测行为。
|
| domain | cybersecurity |
| subdomain | network-security |
| tags | ["network-security","fail2ban","port-scanning","intrusion-prevention","automated-defense"] |
| version | 1.0 |
| author | mahipal |
| license | Apache-2.0 |
使用 Fail2ban 检测端口扫描
适用场景
- 自动封禁对互联网暴露服务器执行端口扫描的 IP 地址
- 通过自动 IP 封禁防御 SSH、HTTP、FTP 及其他服务的暴力破解攻击
- 为日志文件中组织特定的攻击模式创建自定义检测过滤器
- 在流量到达 IDS/IPS 进行深度分析之前,减少自动扫描机器人产生的噪声
- 在网络监控的基础上增加基于主机的自动响应,实现纵深防御
不适用场景:作为唯一的网络安全控制;防御来自大量源 IP 的分布式攻击;替代正确的防火墙规则和网络分段。
前置条件
- 已安装 Fail2ban 0.11+(
fail2ban-client --version)
- 用于 iptables/nftables 操作的 root/sudo 访问权限
- 服务将连接尝试记录到可解析的日志文件(syslog、auth.log、access.log)
- 安装并运行 iptables 或 nftables 作为主机防火墙
- 可选:SMTP 服务器用于封禁事件的邮件通知
工作流程
步骤 1:安装和配置 Fail2ban
sudo apt install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
banaction = iptables-multiport
banaction_allports = iptables-allports
destemail = security@example.com
sender = fail2ban@example.com
mta = sendmail
action = %(action_mwl)s
ignoreip = 127.0.0.1/8 ::1 10.10.0.0/16
backend = systemd
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 7200
findtime = 300
[sshd-ddos]
enabled = true
port = ssh
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 6
bantime = 3600
EOF
步骤 2:创建自定义端口扫描检测过滤器
sudo iptables -N PORTSCAN
sudo iptables -A PORTSCAN -j LOG --log-prefix "PORTSCAN_DETECTED: " --log-level 4
sudo iptables -A PORTSCAN -j DROP
sudo iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST SYN -m state --state NEW \
-m recent --name portscan --set
sudo iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST SYN -m state --state NEW \
-m recent --name portscan --rcheck --seconds 10 --hitcount 20 -j PORTSCAN
sudo tee /etc/fail2ban/filter.d/portscan.conf << 'EOF'
[Definition]
failregex = PORTSCAN_DETECTED: .* SRC=<HOST> DST=\S+ .* DPT=\d+
ignoreregex =
datepattern = {^LN-BEG}
EOF
sudo tee /etc/fail2ban/filter.d/nmap-scan.conf << 'EOF'
[Definition]
failregex = kernel: \[.*\] PORTSCAN_DETECTED: .* SRC=<HOST>
iptables: .* PORTSCAN .* SRC=<HOST>
ignoreregex =
datepattern = {^LN-BEG}
EOF
sudo tee /etc/fail2ban/filter.d/http-scan.conf << 'EOF'
[Definition]
failregex = ^<HOST> .* "(GET|POST|HEAD) /(wp-login|wp-admin|phpmyadmin|admin|.env|xmlrpc|wp-content/uploads).*" (403|404|444)
^<HOST> .* "(GET|POST) /.*\.(php|asp|aspx|jsp|cgi)\?.*" (403|404)
^<HOST> .* "() .*" 400
^<HOST> .* "(GET|POST) /.*" 400
ignoreregex =
datepattern = {^LN-BEG}
EOF
步骤 3:为端口扫描配置 Jail
sudo tee -a /etc/fail2ban/jail.local << 'EOF'
[portscan]
enabled = true
filter = portscan
logpath = /var/log/kern.log
maxretry = 10
findtime = 60
bantime = 86400
banaction = iptables-allports
action = %(action_mwl)s
[nmap-scan]
enabled = true
filter = nmap-scan
logpath = /var/log/kern.log
maxretry = 5
findtime = 30
bantime = 86400
banaction = iptables-allports
action = %(action_mwl)s
[http-scan]
enabled = true
filter = http-scan
logpath = /var/log/nginx/access.log
maxretry = 10
findtime = 300
bantime = 3600
banaction = iptables-multiport
port = http,https
[recidive]
enabled = true
filter = recidive
logpath = /var/log/fail2ban.log
bantime = 604800
findtime = 86400
maxretry = 3
banaction = iptables-allports
action = %(action_mwl)s
EOF
步骤 4:配置高级封禁动作
sudo tee /etc/fail2ban/action.d/iptables-webhook.conf << 'EOF'
[Definition]
actionstart = <iptables> -N f2b-<name>
<iptables> -A f2b-<name> -j RETURN
<iptables> -I <chain> -p <protocol> -j f2b-<name>
actionstop = <iptables> -D <chain> -p <protocol> -j f2b-<name>
<iptables> -F f2b-<name>
<iptables> -X f2b-<name>
actioncheck = <iptables> -n -L <chain> | grep -q 'f2b-<name>[ \t]'
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j <blocktype>
curl -s -X POST "<webhook_url>" \
-H "Content-Type: application/json" \
-d '{"text":"[Fail2ban] Banned <ip> from <name> jail (failures: <failures>)"}'
actionunban = <iptables> -D f2b-<name> -s <ip> -j <blocktype>
[Init]
chain = INPUT
blocktype = DROP
webhook_url = https://hooks.slack.com/services/XXXX/YYYY/ZZZZ
EOF
sudo tee /etc/fail2ban/action.d/escalating-ban.conf << 'EOF'
[Definition]
actionban = <iptables> -I f2b-<name> 1 -s <ip> -j DROP
echo "$(date) BAN <ip> jail=<name> failures=<failures> bantime=<bantime>" >> /var/log/fail2ban-bans.log
actionunban = <iptables> -D f2b-<name> -s <ip> -j DROP
echo "$(date) UNBAN <ip> jail=<name>" >> /var/log/fail2ban-bans.log
EOF
步骤 5:测试和验证检测
sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client status portscan
sudo fail2ban-regex /var/log/kern.log /etc/fail2ban/filter.d/portscan.conf
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/http-scan.conf
nmap -sS -p 1-1000 <target_ip>
sudo fail2ban-client status portscan
sudo iptables -L f2b-portscan -n
sudo fail2ban-client set portscan unbanip <test_ip>
步骤 6:监控和维护
sudo tail -f /var/log/fail2ban.log | grep -E "Ban|Unban"
sudo tee /usr/local/bin/fail2ban-report.sh << 'SCRIPT'
echo "=== Fail2ban 每日报告 $(date) ==="
echo ""
echo "活跃 Jail:"
sudo fail2ban-client status | grep "Jail list"
echo ""
echo "当前封禁 IP:"
for jail in $(sudo fail2ban-client status | grep "Jail list" | sed 's/.*://;s/,//g'); do
count=$(sudo fail2ban-client status "$jail" | grep "Currently banned" | awk '{print $NF}')
if [ "$count" -gt 0 ]; then
echo " $jail: $count 已封禁"
sudo fail2ban-client status "$jail" | grep "Banned IP"
fi
done
echo ""
echo "过去 24 小时——按 Jail 统计封禁数:"
grep "Ban " /var/log/fail2ban. | grep | awk | | -c | -rn
SCRIPT
+x /usr/local/bin/fail2ban-report.sh
| /etc/cron.d/fail2ban-report
apt install iptables-persistent
netfilter-persistent save
核心概念
| 术语 | 定义 |
|---|
| Jail | Fail2ban 配置单元,将过滤器(检测内容)、动作(执行操作)和参数(阈值、时间)组合在一起,用于特定服务 |
| Filter(过滤器) | Fail2ban 应用于日志文件的正则表达式模式,用于识别认证失败、扫描或其他恶意活动 |
| Recidive Jail | 元 Jail,监控 Fail2ban 自身日志以发现重复违规者,对多次被封禁的 IP 应用递进封禁时长 |
| Find Time | Fail2ban 统计匹配日志条目的时间窗口(秒);在 findtime 内达到 maxretry 次失败则触发封禁 |
| Ban Action | IP 被封禁时执行的命令或脚本,通常添加防火墙规则,也可扩展为 Webhook、SIEM 告警或黑名单更新 |
| Ignore IP | 永不封禁的 IP 地址或 CIDR 范围白名单,防止锁定可信网络和监控系统 |
工具与系统
- Fail2ban 0.11+:基于日志解析的入侵防御框架,根据对任意日志文件的模式匹配来封禁 IP 地址
- iptables/nftables:Fail2ban 封禁动作用于在网络层封锁攻击 IP 的 Linux 内核防火墙
- fail2ban-regex:测试工具,用于在部署到生产前对实际日志文件验证过滤器正则表达式
- fail2ban-client:命令行管理工具,用于查询 jail 状态、手动封禁/解封 IP 以及重新加载配置
- rsyslog/syslog-ng:生成 Fail2ban 监控的日志文件的系统日志守护进程
常见场景
场景:防御公开 Web 服务器上的自动扫描
背景:某公司运营着一台公开的 Web 服务器,每天收到数千次自动扫描尝试,机器人探测 /wp-admin、/phpmyadmin、/.env 等易受攻击的路径。安全团队希望在允许合法流量通过的同时自动封锁扫描器。服务器在 Ubuntu 22.04 上运行 Nginx。
方法:
- 安装 Fail2ban 并配置其监控 Nginx 访问日志中的扫描模式(对已知漏洞路径的 404/403 响应)
- 创建自定义
http-scan 过滤器,匹配常见扫描器签名和漏洞探测 URI
- 将 maxretry 设为 10,findtime 为 5 分钟,首次违规的 bantime 为 1 小时
- 启用 recidive jail,对重复违规者将封禁时长升级为 7 天
- 配置 Webhook 通知至 Slack,实时可见封禁活动
- 为到关闭端口的 SYN 数据包添加 iptables 日志规则,检测端口扫描
- 创建每日报告脚本,显示封禁 IP、攻击模式和地理分布
常见陷阱:
- maxretry 设置过低(如 1-2),导致输错 URL 的合法用户被封禁
- 未将监控系统(Nagios、UptimeRobot)加入白名单,其健康检查可能触发过滤器
- 忘记持久化 iptables 规则,重启后所有封禁丢失
- 部署前未用 fail2ban-regex 测试过滤器,导致无匹配或大量误报
输出格式
## Fail2ban 端口扫描防御报告
**服务器**:web-prod-01(203.0.113.50)
**报告周期**:2024-03-15 00:00 至 2024-03-16 00:00 UTC
### 活跃 Jail
| Jail | 过滤器 | 最大重试 | 封禁时长 | 当前封禁 |
|------|--------|-----------|----------|------------------|
| sshd | sshd | 3 | 2 小时 | 12 个 IP |
| portscan | portscan | 10 | 24 小时 | 47 个 IP |
| http-scan | http-scan | 10 | 1 小时 | 89 个 IP |
| recidive | recidive | 3 | 7 天 | 8 个 IP |
### 24 小时摘要
- 封禁事件总数:347
- 唯一 IP 封禁数:156
- 主要攻击来源:CN(67 个 IP),RU(34 个 IP),US(21 个 IP)
- 最频繁攻击服务:HTTP 扫描(214 次封禁)
- 重复违规升级:8 个 IP 被封禁 7 天
### 前 5 封禁 IP
| IP 地址 | Jail | 封禁次数 | 首次发现 | 最后发现 |
|------------|------|-----------|------------|-----------|
| 45.33.32.156 | portscan | 12 | 00:15 | 23:47 |
| 198.51.100.23 | http-scan | 8 | 02:30 | 18:22 |
| 203.0.113.100 | sshd | 6 | 05:12 | 21:33 |