从手动封 IP 到三层自动防御:VPS 舰队安全闭环

当爆破扫描成为日常,手动封 IP 撞不过自动化攻击脚本。记录从 fail2ban 到 auto-ban cron 到 Cloudflare WAF 的三层防御闭环,以及 git 历史凭据脱敏实战。

背景

昨天的安全加固日记写完了 SSL 续签、WAF 规则和 Ansible 初步落地。今天上线 Ansible 巡检后,第一件事就是看看三台机器到底在被怎么打。

结果不出所料——每天都被爆破扫描,只是之前没人系统看过

一、三节点安全现状:数字说话

用 Ansible 一条命令查三节点的 SSH 爆破和 Web 扫描情况:

节点SSH 爆破次数fail2ban 封禁 IPWeb 404 扫描威胁等级
🇦🇺 澳大利亚54713846
🇩🇪 法兰克福197540
🇸🇬 新加坡2517164

新加坡 SSH 爆破最少(25 次),但封禁 IP 最多(171 个)——说明它被大量不同来源的 IP 扫过,攻击者换 IP 的频率远高于另外两个节点。澳大利亚的 Web 扫描有人在打 /phpunit/eval-stdin.php(PHP 反序列化利用)和 /xmlrpc.php(WordPress XML-RPC 攻击),经典自动化扫描套路。

好消息:全部防住了。SSH 爆破被 fail2ban 拦截,Web 扫描返回 404 没打到真实漏洞。

坏消息:fail2ban 只封单 IP,攻击者换 IP 就绕过了。手动封?88 个新攻击 IP,手动封到什么时候?

二、从 fail2ban 到三层防御

第一层:fail2ban(已有,实时)

fail2ban 监控日志,对 SSH 爆破和 Web 扫描的单 IP 临时封禁(通常 1 小时)。这是第一道防线,够快但不够狠——攻击者换个 IP 就回来了。

第二层:auto-ban.sh(新增,每小时)

核心思路:不要封单个 IP,封整个 /24 网段。同一攻击者大概率来自同一个网段,封网段比封 IP 有效得多。

写了一个 shell 脚本,部署到三节点 cron 每小时执行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 核心逻辑(简化版)
{
  # 提取 SSH 爆破 IP
  grep 'Failed password' /var/log/auth.log | extract_ip
  # 提取 Web 404 扫描 IP
  grep ' 404 ' /var/log/nginx/access.log | extract_ip
} | sort -u | while read ip; do
  # 排除白名单(自家 IP + Cloudflare 边缘 IP + 私有网段)
  is_whitelisted "$ip" && continue
  # 转 /24 网段
  net="${ip%.*}.0/24"
  # 跳过已封(state 文件去重)
  grep -qxF "$net" "$STATE" && continue
  # 永久封禁
  ufw deny from "$net" to any
  echo "$net" >> "$STATE"
done

关键设计

设计点为什么
按 /24 网段封单 IP 封了攻击者换 IP 就绕过,网段封更彻底
state 文件去重避免重复封禁同一个网段,ufw 规则不会膨胀
排除 Cloudflare IPCF 边缘 IP 频繁出现在日志里(代理流量),不能封
排除自家 IP四节点互跳频繁,别把自己封了(血泪教训)
ufw deny 全端口SSH + Web 一起封,不让攻击者换端口回来

首次执行结果

节点新封 /24 网段
法兰克福13
澳大利亚65
新加坡91
合计169

加上前一天手动封的 9 个 AWS/Azure 网段,总计 178 个攻击网段被永久封禁。

第三层:Cloudflare WAF(常驻)

CF 层在入口拦截扫描器和恶意路径,不合法的请求根本到不了源站。配合 renhea.com 前哨域名(CF 代理隐藏源站 IP),攻击者连真实 IP 都摸不到。

三层协同

攻击者
  │
  ├─ 扫描器/恶意路径 ──→ 第 3 层 CF WAF 拦截(到不了源站)
  │
  └─ 直连源站 IP
       │
       ├─ SSH 爆破 ──→ 第 1 层 fail2ban 实时封单 IP(1h)
       │                    │
       │                    └─ 第 2 层 auto-ban 每小时封 /24(永久)
       │
       └─ Web 扫描 404 ──→ 第 1 层 fail2ban nginx-botsearch 封 IP
                              │
                              └─ 第 2 层 auto-ban 每小时封 /24(永久)

三、Ansible 部署:一条命令推三节点

手动 SSH 到三台机器部署脚本太低效。用 Ansible playbook 一键搞定:

1
2
3
4
5
6
- name: Deploy auto-ban script to fleet
  hosts: fleet
  tasks:
    - copy: src=auto-ban.sh dest=/usr/local/bin/ mode=0755
    - shell: /usr/local/bin/auto-ban.sh      # 首次立即执行
    - cron: name="auto-ban" minute=30 hour=*  # 每小时 :30

ansible-playbook deploy-auto-ban.yml 一条命令,三节点同时部署脚本 + 执行 + 配 cron。这就是 Ansible 的价值——把重复操作变成声明式配置

踩坑:Ansible copy 模块的相对路径基于 playbook 所在目录,不是 ansible.cfg 目录。用绝对路径最省心。

四、git 历史凭据脱敏

部署完防御机制,回头检查项目仓库——发现自己犯了个低级错误:代理 UUID 和真实 IP 都提交到 git 历史里了

暴露了什么

敏感信息位置风险
VLESS 代理 UUIDconfig/xray/*.md攻击者可冒充代理节点
服务器真实 IP多个文件绕过 CF 直连源站
分享链接(含 UUID+路径)config/xray/*.md完整代理凭证泄露

修复方案

NAS 的 git 版本太老不支持 git filter-branch,而且仓库只有 4 个 commit——直接重建历史最干净

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# 1. 备份文件(不含 .git)
cp -r . /tmp/clean && rm -rf /tmp/clean/.git

# 2. 写好 .gitignore(排除敏感文件)
# config/xray/*.md  ← 含 UUID
# docs/WORKFLOW_APPEND.md  ← 含真实 IP
# scripts/deploy.sh  ← 含真实 IP

# 3. 删旧历史,重新 init
rm -rf .git && git init && git add -A

# 4. 验证无敏感信息再提交
git grep -E "uuid|password|token|[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+"
git commit -m "feat: 脱敏版"

教训

  1. .gitignore 要在第一次 commit 前就写好,不是事后补
  2. 配置文件和文档要分离:通用配置入 git,真实凭证(UUID/IP/密码)只存节点本机
  3. 重建历史只适合小仓库(几个 commit),大仓库必须用 git filter-branch 或 BFG

五、踩坑总结

原因解决
fail2ban allports 误封自家 IP舰队节点互跳触发 SSH 爆破规则四节点 IP 加入 ignoreip 白名单
Ansible copy 找不到文件相对路径基于 playbook 目录用绝对路径
git 凭据泄露.gitignore 写晚了重建历史 + 敏感配置不入库
auto-ban 封到 Cloudflare IPCF 边缘 IP 出现在 nginx 日志脚本排除 CF IP 段

总结

从昨天手动检查 SSL、手动配 WAF,到今天 Ansible 一键巡检、auto-ban 自动封禁——运维的本质就是把手动操作变成自动化流程

三层防御体系现在完整了:

  • fail2ban:实时封单 IP,快但不持久
  • auto-ban cron:每小时封 /24 网段,持久且自动去重
  • Cloudflare WAF:入口拦截,攻击者摸不到源站

攻击者用自动化脚本扫描,我们就用自动化脚本防御。这不是军备竞赛——这是把重复劳动交给机器


本文已脱敏,不含真实 IP、UUID 或凭证。

使用 Hugo 构建
主题 StackJimmy 设计