# Detecting Port Scanning With Fail2ban

> 使用自定义过滤器和动作配置 Fail2ban，检测端口扫描活动、SSH 暴力破解尝试和网络侦察，自动封禁攻击 IP 并向安全团队告警可疑的网络探测行为。

- Skill: `killvxk/detecting-port-scanning-with-fail2ban` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add killvxk/detecting-port-scanning-with-fail2ban`
- Raw SKILL.md: https://api.skillmd.com/api/skills/killvxk/detecting-port-scanning-with-fail2ban/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: Apache-2.0
- Author: killvxk (https://skillmd.com/u/killvxk)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/killvxk/detecting-port-scanning-with-fail2ban

---

# 使用 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

```bash
# 安装 Fail2ban
sudo apt install -y fail2ban

# 创建本地配置（不要直接编辑 jail.conf）
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

# 配置全局默认值
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# 封禁时长（默认 1 小时，对重复违规者逐步升级）
bantime = 3600
# 检测窗口
findtime = 600
# 封禁前的最大失败次数
maxretry = 5
# 使用 iptables 的封禁动作
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

# 在可用时使用 systemd journal 后端
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：创建自定义端口扫描检测过滤器

```bash
# 为丢弃的连接创建 iptables 日志规则
sudo iptables -N PORTSCAN
sudo iptables -A PORTSCAN -j LOG --log-prefix "PORTSCAN_DETECTED: " --log-level 4
sudo iptables -A PORTSCAN -j DROP

# 记录到关闭端口的 SYN 数据包（表明有扫描）
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

# 为端口扫描创建 Fail2ban 过滤器
sudo tee /etc/fail2ban/filter.d/portscan.conf << 'EOF'
[Definition]
# 匹配 iptables 端口扫描日志条目
failregex = PORTSCAN_DETECTED: .* SRC=<HOST> DST=\S+ .* DPT=\d+
ignoreregex =
datepattern = {^LN-BEG}
EOF

# 为通过内核日志检测 Nmap 创建 Fail2ban 过滤器
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

# 为 HTTP 扫描/探测创建过滤器
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

```bash
# 将端口扫描 jail 添加到 jail.local
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：配置高级封禁动作

```bash
# 创建自定义动作，封禁并发送 Webhook 通知
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：测试和验证检测

```bash
# 重启 Fail2ban
sudo systemctl restart fail2ban

# 验证 jail 是否激活
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

# 测试 HTTP 扫描过滤器
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
# 应显示测试 IP 在封禁列表中

# 检查 iptables 中的封禁规则
sudo iptables -L f2b-portscan -n

# 解封测试 IP
sudo fail2ban-client set portscan unbanip <test_ip>
```

### 步骤 6：监控和维护

```bash
# 实时查看封禁活动
sudo tail -f /var/log/fail2ban.log | grep -E "Ban|Unban"

# 生成每日摘要报告
sudo tee /usr/local/bin/fail2ban-report.sh << 'SCRIPT'
#!/bin/bash
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.log | grep "$(date +%Y-%m-%d)" | awk '{print $NF}' | sort | uniq -c | sort -rn
SCRIPT
chmod +x /usr/local/bin/fail2ban-report.sh

# 定时每日报告
echo "0 8 * * * root /usr/local/bin/fail2ban-report.sh | mail -s 'Fail2ban Report' security@example.com" | sudo tee /etc/cron.d/fail2ban-report

# 跨重启持久化 iptables 规则
sudo apt install iptables-persistent
sudo 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。

**方法**：
1. 安装 Fail2ban 并配置其监控 Nginx 访问日志中的扫描模式（对已知漏洞路径的 404/403 响应）
2. 创建自定义 `http-scan` 过滤器，匹配常见扫描器签名和漏洞探测 URI
3. 将 maxretry 设为 10，findtime 为 5 分钟，首次违规的 bantime 为 1 小时
4. 启用 recidive jail，对重复违规者将封禁时长升级为 7 天
5. 配置 Webhook 通知至 Slack，实时可见封禁活动
6. 为到关闭端口的 SYN 数据包添加 iptables 日志规则，检测端口扫描
7. 创建每日报告脚本，显示封禁 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 |
```

