上一篇 下一篇 分享链接 返回 返回顶部

香港服务器如何通过 Ubuntu 22.04 的 UFW 与 Fail2ban 双重防护,抵御跨境暴力破解

发布人:Minchunlin 发布时间:2025-08-26 11:09 阅读量:652


周三凌晨 02:17,我的手机收到Prometheus 告警信息:auth_fail_rate > 200/min。香港这台对外的跳板与 Web 前端节点被“照顾”了——来自多个海外 ASN 的 SSH 暴力破解在两分钟内把 auth.log 挤满。机房冷得出奇,机柜风扇的低频嗡嗡声像一口“白噪音井”。我披上外套坐到KVM前,边喝半杯凉掉的拿铁边做下列操作。这篇文章就是那一夜的完整复盘与手把手教程。

一、环境与目标(现场参数)

项目 参数/说明
机房/位置 香港葵涌,Tier III
服务器机型 Supermicro 1U(AMD EPYC 7313P,16C/32T,3.0GHz)
内存/存储 64GB ECC,2×1.92TB NVMe(RAID1,mdadm)
网卡/公网 Dual 10GbE(bonding active-backup),/29 IPv4,单 IP 对外
带宽计费 1Gbps 95 分位
OS Ubuntu Server 22.04.4 LTS(Jammy),Kernel 5.15
关键服务 Nginx 反向代理、跳板 SSH、若干容器(Docker)
安全基线 已做最小化安装、SSH Key 登录、禁 root 密码登录
目标 UFW(静态边界策略)+ Fail2ban(动态封禁) 在不影响业务的前提下,迅速压制跨境暴力破解,并可持续运营(可观测、可回滚、可扩展)。

二、威胁画像(那一夜我看到什么)

/var/log/auth.log(节选):

Aug 21 02:16:05 hk-frontend sshd[17832]: Failed password for invalid user admin from 185.234.219.45 port 51832 ssh2
Aug 21 02:16:06 hk-frontend sshd[17836]: Failed password for root from 45.67.123.9 port 43150 ssh2
Aug 21 02:16:07 hk-frontend sshd[17841]: Failed password for ubuntu from 103.149.12.74 port 37522 ssh2
Aug 21 02:16:07 hk-frontend sshd[17845]: Failed password for invalid user test from 190.2.145.20 port 59210 ssh2

当时的临时统计(5 分钟窗口):

指标 数值
SSH 失败认证 1,164 次 / 5 分钟
唯一来源 IP 592 个
国家/地区分布(Top5) RU、BR、US、VN、IN
Top 端口 22/TCP
误报风险 低(我们从固定办公与堡垒机段登录)

三、策略设计图(简述)

UFW:把默认策略、服务白名单、跨境访问边界(可选的 Geo/AS 级围栏)固化在内核过滤链前段,静态兜底。

Fail2ban:基于系统日志/Journal,按 findtime/maxretry/bantime 动态封禁,处理突发与横向扩散。

协同:Fail2ban 的 banaction 直接调用 UFW,所有动态封禁都进 UFW 的 deny 规则栈;Docker 流量通过 DOCKER-USER 链统一裁决,避免容器绕过主机防火墙。

四、实操步骤(从 0 到 1,可复制)

注意:以下操作默认你已具备 SSH Key 登录,先加白再启用防火墙,否则可能把自己“锁”在机房外。

4.1 基础加固(10 分钟完成)

编辑 /etc/ssh/sshd_config(关键项):

Port 22
PermitRootLogin prohibit-password
PasswordAuthentication no
PubkeyAuthentication yes
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 60
ClientAliveCountMax 3
KexAlgorithms curve25519-sha256@libssh.org
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
UseDNS no

sudo systemctl reload ssh

是否换端口?可选。更换非 22 端口能显著降噪,但不是安全边界的替代;本文保持 22,便于与现有监控与资产清单一致。

4.2 安装与初始化 UFW(静态边界)

sudo apt update
sudo apt install -y ufw
# 默认策略:禁止入站、放行出站
sudo ufw default deny incoming
sudo ufw default allow outgoing

# 先放行自己的固定来源(示例网段/地址请替换)
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp comment 'Office'
sudo ufw allow from 198.51.100.10 to any port 22 proto tcp comment 'Bastion'

# 业务端口
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# 适度记录
sudo ufw logging medium

# 最后才启用
sudo ufw enable
sudo ufw status numbered

UFW 规则清单(示例)

# 规则 说明
1 22/tcp ALLOW IN 203.0.113.0/24 办公网段
2 22/tcp ALLOW IN 198.51.100.10 堡垒机
3 80/tcp ALLOW IN Anywhere Web
4 443/tcp ALLOW IN Anywhere Web
5 Anywhere DENY IN 默认拒绝

如果你必须允许全球 SSH(例如合作方动态 IP),先不放开 22;交给 Fail2ban 动态限速/封禁,或者只临时放行。

4.3 安装与配置 Fail2ban(动态封禁)

sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban

创建 /etc/fail2ban/jail.local:

[DEFAULT]
# 使用 systemd 后端读 Journal,性能与完整性更好
backend = systemd

# 动态封禁参数(指数回退,适合高噪声暴力破解)
findtime = 5m
maxretry = 4
bantime = 1h
bantime.increment = true
bantime.factor = 8
bantime.rndtime = 10m

# 与 UFW 协同:所有封禁都写入 UFW
banaction = ufw

# 信任来源(不要被自己“打脸”)
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24 198.51.100.10

destemail = secops@example.com
sender = fail2ban@hk-frontend
mta = sendmail

[sshd]
enabled = true
port = 22
logpath = %(sshd_log)s
maxretry = 4

# 可选:对 Web 登录、反向代理等做额外保护(如有)
[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/*access.log

# 二次违规大棒:反复出现者拉长封禁
[recidive]
enabled = true
logpath = /var/log/fail2ban.log
bantime = 1w
findtime = 1d
maxretry = 5

重启并查看:

sudo systemctl restart fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd

典型输出(节选)

Status for the jail: sshd
|- Filter
|  |- Currently failed: 0
|  `- Total failed:     248
`- Actions
   |- Currently banned: 37
   `- Total banned:     37

4.4 Docker/容器流量不要绕过防火墙(关键坑)

Docker 会创建自己的 FORWARD/NAT 规则,容器入站可能绕过 UFW。做两件事:

确保 Docker 使用 iptables(默认是):

/etc/docker/daemon.json(没有就新建)

{
  "iptables": true,
  "log-driver": "json-file",
  "log-opts": { "max-size": "100m", "max-file": "3" }
}

sudo systemctl restart docker

统一入口:在 DOCKER-USER 链前置丢弃被 Fail2ban/UFW 封禁的来源(UFW 会把 deny 写进 filter 链,保险起见加一道):

# 这条规则把 DOCKER-USER 链挂到 UFW 用户链前(一次性)
sudo iptables -I DOCKER-USER -j RETURN
# 如果你使用的是 nft 后端,建议改用 fail2ban 的 nftables 动作或直接依赖 UFW。

实战里,我更多依赖 banaction=ufw,统一从主机边界裁决,容器自然受保护。

4.5 进阶:跨境访问围栏(可选 Geo/AS 策略)

有些场景(如运维 SSH)只允许少数国家/网段访问更稳。思路:用 ipset 维护“允许列表”,在 UFW 的 before.rules 前置匹配,不在集合内的一律丢弃(对 SSH 生效即可,Web 仍全球放行)。

准备 ipset:

sudo apt install -y ipset iptables-persistent
# IPv4 允许集(示例)
sudo ipset create geo_allow_v4 hash:net family inet
# 持久化文件
sudo mkdir -p /etc/ipset.d

把你允许的国家/网段(例如 HK/CN/SG 的精选企业/办公出口段)写入 /etc/ipset.d/geo_allow_v4.list,格式一行一个 CIDR:

1.2.3.0/24
5.6.7.0/24
203.0.113.0/24
198.51.100.10/32

加载脚本 /usr/local/sbin/ipset-geo-allow-load.sh:

#!/usr/bin/env bash
set -euo pipefail
ipset flush geo_allow_v4 || ipset create geo_allow_v4 hash:net family inet
while read -r cidr; do
  [[ -z "$cidr" || "$cidr" =~ ^# ]] && continue
  ipset add geo_allow_v4 "$cidr"
done < /etc/ipset.d/geo_allow_v4.list
/sbin/iptables-save > /etc/iptables/rules.v4

sudo chmod +x /usr/local/sbin/ipset-geo-allow-load.sh

systemd 单元 /etc/systemd/system/ipset-geo-allow.service:

[Unit]
Description=Load geo allow ipset
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/ipset-geo-allow-load.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

sudo systemctl enable --now ipset-geo-allow.service

在 UFW 的 /etc/ufw/before.rules 前置规则(仅对 SSH):

*filter
:ufw-geo-allow - [0:0]

# 非允许集访问 SSH -> 丢弃(放在 ufw-user-input 入口之前)
-A ufw-geo-allow -p tcp --dport 22 -m set ! --match-set geo_allow_v4 src -j DROP

# 挂接到 UFW 的用户输入链
-A ufw-user-input -j ufw-geo-allow

# 下面保留 UFW 原有内容……
COMMIT

sudo ufw reload

这样做的好处:Fail2ban 负责突发与未知来源,而 ipset 负责常态边界。SSH 的全球噪音会瞬间“静音”。

4.6 验证与压测(当场就能看到效果)

从未经允许的境外云主机连 22:

预期:直接超时或被 DROP。

从允许网段连续 4 次错误密码:

预期:Fail2ban 触发,IP 被写入 UFW deny,Currently banned +1。

检查状态:

sudo ufw status numbered
sudo tail -f /var/log/ufw.log
sudo fail2ban-client status sshd
sudo ipset list geo_allow_v4

5 分钟后指标(真实效果)

指标 之前 之后
SSH 失败认证次数(5 min) 1,164 23
唯一来源 IP 592 11
Fail2ban 当前封禁 0 37
Web QPS 抖动 明显 无明显变化
CPU/内存开销 +3~5% / +20MB 可接受

五、持续运营:监控与报警(别等半夜才知道)

Fail2ban 邮件报警(已在 jail.local 配置 destemail/mta)。

Prometheus + node_exporter:抓 auth_failed_total、iptables_drop_total(可自定义导出器或日志抓取),Grafana 面板直观看“暴力破解风暴”。

每日封禁报表(简单 cron):

/usr/local/sbin/f2b-report.sh:

#!/usr/bin/env bash
echo "===== Fail2ban Daily Report - $(date) ====="
sudo fail2ban-client status sshd
sudo fail2ban-client get sshd banip

(crontab -l; echo '0 8 * * * /usr/local/sbin/f2b-report.sh | mail -s "F2B Daily" secops@example.com') | crontab -

六、我踩过的坑 & 解决手记

先开 UFW 再加白,锁自己

解决:先加白再 enable,并保留机房外带外 KVM/ILO。

Docker 绕过 UFW

解决:统一依赖 banaction=ufw,并检查 DOCKER-USER 链;必要时用 nftables 版动作。

nftables/iptables 混用疑虑(Ubuntu 22.04 默认 iptables-nft)

解决:UFW 已兼容;Fail2ban 选 banaction=ufw 或使用 nftables 动作,避免自己维护生僻规则。

过度 Geo 封锁导致合作方连不上

解决:SSH 用白名单,Web 仍全球开放;同时给合作方预留 bastion。

日志轮转导致 Fail2ban 抽风

解决:用 backend=systemd 直接读 Journal,避免路径变动。

IPv6 忽略

解决:若有 v6 公网,同步配置 /etc/ufw/before6.rules 与 geo_allow_v6(family inet6),避免只“堵一半”。

七、常用运维指令速查

# UFW
sudo ufw status numbered
sudo ufw delete <num>
sudo ufw insert 1 deny from <ip> to any comment 'manual block'

# Fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd
sudo fail2ban-client set sshd unbanip <ip>
sudo tail -f /var/log/fail2ban.log

# ipset
sudo ipset list geo_allow_v4
sudo ipset add geo_allow_v4 203.0.113.0/24

八、参数模板(可直接复用)

Fail2ban(稳健值)

参数 推荐值 说明
findtime 5m 观察窗口
maxretry 4 4 次错误触发
bantime 1h 初始封禁时长
bantime.factor 8 指数回退
recidive 开启 复犯延长到 1 周

UFW(SSH 策略)

推荐值
Incoming 默认 deny
允许来源 办公网段、堡垒机
Web 端口 80/443 allow
记录级别 medium
Geo 围栏 仅 SSH 加固(可选)

九、回滚与应急(两条命令,保命绳)

# 临时全开(用于紧急排障,5 分钟内撤)
sudo ufw default allow incoming
# 或直接停 Fail2ban(别忘了恢复)
sudo systemctl stop fail2ban

十、收工时刻

凌晨 03:06,Grafana 上 SSH 失败曲线像被人一刀切下来,Fail2ban 的封禁计数还在缓慢上涨,但已经不影响任何业务。机房里只剩空调和风扇的白噪音。我合上 KVM,给自己又倒了一杯热咖啡,把这次变更写进了变更单与 SOP。安全不是一次性的设置,而是一套可持续的“运营体系”:静态边界(UFW)、动态响应(Fail2ban)、数据闭环(监控与报表)、再到可回滚的流程。第二天上午,业务方只注意到“SSH 登陆更快了”。我知道,那一夜我们又多了一层可贵的“安静”。

附:完整配置清单(便于复制)

/etc/fail2ban/jail.local(参考上文块)

/etc/ufw/before.rules(加入 SSH 前置 Geo 检查段)

*filter
:ufw-geo-allow - [0:0]
-A ufw-geo-allow -p tcp --dport 22 -m set ! --match-set geo_allow_v4 src -j DROP
-A ufw-user-input -j ufw-geo-allow
COMMIT

/usr/local/sbin/ipset-geo-allow-load.sh 与 systemd 单元(见上文)
目录结构
全文