Featured image of post fail2ban 防 SSH/FRP/Nginx 爆破实战:日志正则匹配 + iptables 拉黑

fail2ban 防 SSH/FRP/Nginx 爆破实战:日志正则匹配 + iptables 拉黑

fail2ban 0.10 安装配置、自定义 filter、jail.local 多协议、FRP 日志拉黑真实 IP、Nginx access log 拦截、systemd 守护、客户端管理命令详解

背景

公网上的 SSH 22 端口,每天被全球扫描器爆破几千次。改端口可以挡一部分,但治标不治本。fail2ban 通过扫描日志匹配失败登录,自动调用 iptables/nftables 拉黑攻击 IP——简单粗暴有效,是 Linux 服务器"装了不亏"的标配。

适用场景

  • SSH/RDP/FTP 暴破防御
  • FRP 反向代理的"真实攻击者 IP"拉黑(frpc 只看到 127.0.0.1,必须在 frps 端 ban)
  • Nginx 反代日志里的恶意访问拦截
  • 自定义服务日志的异常行为检测

前置知识

  • Linux 基础(systemd、iptables)
  • 正则表达式基础(fail2ban filter 用的就是正则)

一、fail2ban 工作原理

1
2
3
4
5
6
7
服务日志(sshd/auth.log/secure)
[filter.d/*.conf 正则匹配]  →  失败次数超阈值
[jail.local 触发动作]  →  iptables/nftables DROP
[封禁时间到]  →  自动解封

关键组件

  • filter:正则规则(/etc/fail2ban/filter.d/
  • jail:封禁策略(/etc/fail2ban/jail.local
  • action:触发动作(iptables、route、cloudflare API 等)
  • bantime / findtime / maxretry:封禁时长 / 检测窗口 / 失败次数阈值

二、安装

2.1 CentOS

1
2
yum -y install epel-release
yum -y install fail2ban

2.2 Ubuntu / Debian

1
apt install fail2ban

2.3 启动 + 开机自启

1
2
systemctl enable --now fail2ban
systemctl status fail2ban

验证 iptables 规则已加载:

1
2
iptables -L -n
# 应看到 f2b-sshd 等 chain

三、配置 SSH 防护(默认即开)

/etc/fail2ban/jail.local注意是 .local 不是 .conf.local 优先级高):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[DEFAULT]
# 忽略的 IP(公司出口、内网)
ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16
# 默认参数(每个 jail 可单独覆盖)
bantime  = 24h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
# filter 内置 sshd.conf(Debian/Ubuntu) / sshd.conf(CentOS 在 filter.d/ 选)
filter  = sshd
backend = systemd
# 日志路径(按发行版二选一)
logpath  = %(sshd_log)s

%(sshd_log)s 是 fail2ban 内置变量,CentOS 是 /var/log/secure,Ubuntu 是 /var/log/auth.log,不用手填。

reload 生效

1
2
3
fail2ban-client reload
# 或
systemctl restart fail2ban

验证

1
2
fail2ban-client ping          # 返回 Server replied: pong
fail2ban-client status sshd   # 看当前被 ban 的 IP

四、FRP 反代场景的关键配置

核心痛点:frpc 看到的访问者 IP 永远是 127.0.0.1(因为 frps 是 localhost 转发的),必须在 frps 所在的 VPS 上配 fail2ban,按 frps 日志里的真实 IP 拉黑。

4.1 启用 FRP 日志

/usr/local/games/rp/frps.ini

1
2
3
4
5
[common]
bind_port = 7000
log_file = /var/log/frps.log
log_level = info
log_max_days = 7

4.2 编写 filter

/etc/fail2ban/filter.d/frps.conf

1
2
3
4
[Definition]
# frps 日志格式: get a user connection [1.2.3.4:5678]
failregex = ^.*get a user connection \[<HOST>:[0-9]*\]
ignoreregex = ^.*\[.*gateway.*

<HOST> 是 fail2ban 的内置占位符,匹配 IP 段。

4.3 编写 jail

/etc/fail2ban/jail.local 追加:

1
2
3
4
5
6
7
8
[frps]
enabled  = true
filter   = frps
logpath  = /var/log/frps.log
findtime = 3m
maxretry = 3
bantime  = 120m
action   = iptables-allports[name=frps, protocol=all]

多个协议分离 jail(更精细):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[frps-ssh-ban]
enabled  = true
filter   = frps-ssh-ban
logpath  = /var/log/frps.log
findtime = 3m
maxretry = 3
bantime  = 120m
action   = iptables-allports[name=frps-ssh-ban, protocol=tcp]

[frps-rdp-ban]
enabled  = true
filter   = frps-rdp-ban
logpath  = /var/log/frps.log
findtime = 3m
maxretry = 6
bantime  = 30m
action   = iptables-allports[name=frps, protocol=tcp]

[frps-ftp-ban]
enabled  = true
filter   = frps-ftp-ban
logpath  = /var/log/frps.log
findtime = 5m
maxretry = 30
bantime  = 60m
action   = iptables-allports[name=frps, protocol=tcp]

filter 内容(按代理名分组):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /etc/fail2ban/filter.d/frps-ssh-ban.conf
[Definition]
failregex = ^.*\[.*ssh.*\] get a user connection \[<HOST>:[0-9]*\]
ignoreregex = ^.*\[.*gateway.*

# /etc/fail2ban/filter.d/frps-rdp-ban.conf
[Definition]
failregex = ^.*\[.*RDP.*\] get a user connection \[<HOST>:[0-9]*\]
ignoreregex =

# /etc/fail2ban/filter.d/frps-ftp-ban.conf
[Definition]
failregex = ^.*\[.*FTP_21.*\] get a user connection \[<HOST>:[0-9]*\]
ignoreregex =

测试正则匹配(上线前必做):

1
2
fail2ban-regex /var/log/frps.log /etc/fail2ban/filter.d/frps.conf
# 末尾会输出匹配行数

五、Nginx 日志防护

如果用 Nginx 反代 RDP/SSH(stream 四层 + 自定义 log_format):

5.1 自定义日志格式

1
2
3
4
5
log_format proxy '[$time_local] $protocol status:$status, '
                 'bytes client:$bytes_sent/$bytes_received $session_time, '
                 'client: $remote_addr, upstream:"$upstream_addr", '
                 'bytes upstream:$upstream_bytes_sent $upstream_bytes_received';
access_log /var/log/nginx/proxy.log proxy;

5.2 写 filter

1
2
3
4
# /etc/fail2ban/filter.d/nginx-3399.conf
[Definition]
failregex = ^.*client: <HOST>, upstream:"10\..*:3389"
ignoreregex =

5.3 写 jail

1
2
3
4
5
6
7
8
[nginx-3399]
enabled  = true
filter   = nginx-3399
logpath  = /var/log/nginx/proxy.log
findtime = 5m
maxretry = 10
bantime  = 60m
action   = iptables-multiport[name=nginx-3399, port="3399", protocol=tcp]

六、多日志文件支持

轮转日志场景(按天/按大小切割):

1
2
3
4
5
6
7
# 通配符
logpath = /var/log/vpn_*.log

# 多个文件
logpath = /var/log/vpn_20191007.log
          /var/log/vpn_20191008.log
          /var/log/vpn_20191009.log

⚠️ 等号要对齐(fail2ban INI parser 严格)。


七、日常管理命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 服务状态
fail2ban-client ping

# 列出所有 jail
fail2ban-client status

# 查看指定 jail
fail2ban-client status sshd

# 手动封 IP
fail2ban-client set sshd banip 1.2.3.4

# 手动解封
fail2ban-client set sshd unbanip 1.2.3.4

# 解封所有
fail2ban-client unban --all

# 重载配置(不重启服务)
fail2ban-client reload

# 看日志
tail -f /var/log/fail2ban.log

直接操作 iptables(不进 fail2ban 的快封):

1
2
3
4
5
6
7
8
# 手动封
iptables -A INPUT -s 1.2.3.4 -j DROP

# 查询
iptables -L -n --line-number

# 删除 INPUT 第 3 条规则
iptables -D INPUT 3

八、常见坑

8.1 封了之后 SSH 自己进不去

症状:把整个机房 IP 段封了。

对策

  • ignoreip 把内网/常用出口 IP 段加白名单
  • 至少留一个"急救"通道(如 IPMI、Console、备用跳板)

8.2 重启服务后封禁丢失

iptables 规则在重启后清空。对策

1
2
3
4
5
6
7
8
# CentOS
yum install iptables-services
systemctl enable iptables
service iptables save

# Ubuntu
apt install iptables-persistent
netfilter-persistent save

8.3 多网卡机器

chain = INPUT 即可默认全局生效。如果要区分内/外网卡,用 chain = FORWARD 配合路由。

8.4 日志被 logrotate 改名后 fail2ban 跟丢

backend = pollingbackend = systemd(systemd 日志自动跟踪),或者在 /etc/logrotate.d/postrotate 通知 fail2ban。


九、卸载

1
2
3
4
5
6
# 清掉所有 fail2ban iptables 规则
iptables -L | grep f2b | awk '{print $NF}' | xargs -I{} iptables -F {}

/etc/init.d/fail2ban stop   # 或 systemctl stop fail2ban
apt remove fail2ban -y
apt purge fail2ban -y       # 删配置

小结

fail2ban 0.10+ 的核心是 filter 正则 + jail 策略

  1. SSH 默认就开apt install 装完自带)
  2. FRP 场景最关键——必须在 frps 端按 frps.log 真实 IP 拉黑
  3. jail.local 永远比 jail.conf 优先(升级不会覆盖)
  4. 测试正则fail2ban-regex 上线前必跑
  5. iptables 持久化 否则重启失效

下一步

  • 配合 cloudflare action 封 CDN 后面的恶意 IP
  • recidive jail 二次封禁"被解封又来"的攻击者
  • 集中化管理:多台机器用 fail2ban-web 或自建 dashboard

2024 视角:fail2ban 仍是经典,但 nftables 时代来了

2015 那篇基于 iptables2024 视角下 nftables 已成事实标准——fail2ban 1.0+(2022-10 发布)已支持 nftables。

一、nftables 替代 iptables 是大势所趋

  • RHEL 8/9 / Fedora / Ubuntu 22.04+ / Debian 12+ 默认都是 nftablesiptables 命令其实是 nftables 的兼容层)。
  • fail2ban 1.0.x(2022-10 起)支持 banaction = nftables-multiport
1
2
3
4
5
# /etc/fail2ban/jail.local
[DEFAULT]
banaction = nftables-multiport
banaction_allports = nftables-allports
chain     = input
  • nftables 优势
    • 原子化更新(nft 是事务性的,iptables 改一条规则要逐条 add/delete)
    • 规则集更紧凑(nft 的语法比 iptables 短 30-50%)
    • 内建 set / map(无需 ipset 单独装)
1
2
# nftables 查 fail2ban 规则
nft list chain inet filter f2b-sshd

二、recidive 监狱——“二次封禁"防反复

2015 那篇提到 recidive。2024 实际配置

1
2
3
4
5
6
7
8
9
# /etc/fail2ban/jail.local
[recidive]
enabled  = true
filter   = recidive
logpath  = /var/log/fail2ban.log
bantime  = 1w
findtime = 1d
maxretry = 5
banaction = %(banaction)s

效果:被解封后又来攻击的 IP,封一周

三、Cloudflare Action——封 CDN 后面的 IP

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# /etc/fail2ban/action.d/cloudflare.conf
[Definition]
actionban = curl -s -X POST "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules" \
              -H "Authorization: Bearer <API_TOKEN>" \
              -H "Content-Type: application/json" \
              --data '{"mode":"block","configuration":{"target":"ip","value":"<ip>"},"notes":"Fail2ban"}'
actionunban = curl -s -X DELETE "https://api.cloudflare.com/client/v4/zones/<ZONE_ID>/firewall/access_rules/rules/<RULE_ID>" \
              -H "Authorization: Bearer <API_TOKEN>"

[Init]
RULE_ID =
1
2
3
4
[sshd-cloudflare]
enabled  = true
filter   = sshd
action   = cloudflare

四、fail2ban + Telegram 告警

1
2
3
4
5
# /etc/fail2ban/action.d/telegram.conf
[Definition]
actionban = curl -s -X POST "https://api.telegram.org/bot<TOKEN>/sendMessage" \
              -d chat_id=<CHAT_ID> \
              -d text="Fail2ban: <ip> banned (<failures> failures)"

五、fail2ban 1.1 的新特性(2023-10)

  • 持久化 SQLite 数据库dbfile = /var/lib/fail2ban/fail2ban.sqlite3)——重启后封禁不丢。
  • database 升级:以前是 BerkeleyDB(要装 python3-pyasn1),现在用 SQLite。
  • 正则引擎优化:Python 3.11+ 提速 20%。

六、CrowdSec:fail2ban 的"现代继任者”

  • CrowdSec(Go 写的现代版 fail2ban)2024 已成为更激进的选择
1
2
3
4
# 装
curl -s https://packagecloud.io/install/repositories/crowdsec/crowdsec/script.deb.sh | sudo bash
sudo apt install crowdsec
sudo apt install crowdsec-firewall-bouncer-iptables
  • 核心优势

    • 社区共享黑名单(CrowdSec 中心服务器聚合)
    • Go 写(性能比 Python fail2ban 高 10 倍)
    • bouncer 架构(解析 + 拉黑分离)
    • PostgreSQL 存储(可查询历史)
  • 2024 实践:fail2ban + CrowdSec 可以共存——fail2ban 处理本地 log,CrowdSec 接收社区信号。

七、fail2ban 在 Docker 时代的"困境"

  • fail2ban 装在 host 上时,它看不到容器里的日志(除非 mount log 目录到 host)。
  • fail2ban 装在容器里时,它改不了 iptables/nftables(容器网络是隔离的)。
  • 2024 推荐方案
    • 方案 A:fail2ban 装 host,把容器日志 mount 出来(适合简单场景)
    • 方案 B:用 CrowdSec + bouncer in container(更适合 K8s)
    • 方案 C:直接上 WAF(Cloudflare / ModSecurity)—— 应用层防护

源文档os/linux/第三方tools/safe/fail2ban/fail2ban.md(filter 模板、jail.local 多协议、FRP 集成)

使用 Hugo 构建
主题 StackJimmy 设计