Files
blog/content/posts/three-layer-defense.md

191 lines
7.4 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: "从手动封 IP 到三层自动防御:VPS 舰队安全闭环"
description: "当爆破扫描成为日常,手动封 IP 撞不过自动化攻击脚本。记录从 fail2ban 到 auto-ban cron 到 Cloudflare WAF 的三层防御闭环,以及 git 历史凭据脱敏实战。"
date: 2026-08-02T10:50:00+08:00
draft: false
tags:
- 运维
- 安全
- Ansible
- UFW
- Cloudflare
---
## 背景
昨天的[安全加固日记](/posts/fleet-security-day/)写完了 SSL 续签、WAF 规则和 Ansible 初步落地。今天上线 Ansible 巡检后,第一件事就是看看三台机器到底在被怎么打。
结果不出所料——**每天都被爆破扫描,只是之前没人系统看过**。
## 一、三节点安全现状:数字说话
用 Ansible 一条命令查三节点的 SSH 爆破和 Web 扫描情况:
| 节点 | SSH 爆破次数 | fail2ban 封禁 IP | Web 404 扫描 | 威胁等级 |
|------|:--:|:--:|:--:|:--:|
| 🇦🇺 澳大利亚 | 547 | 138 | 46 | 中 |
| 🇩🇪 法兰克福 | 197 | 54 | 0 | 低 |
| 🇸🇬 新加坡 | 25 | 171 | 64 | 中 |
新加坡 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 每小时执行:
```bash
# 核心逻辑(简化版)
{
# 提取 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 IP | CF 边缘 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 实时封单 IP1h
│ │
│ └─ 第 2 层 auto-ban 每小时封 /24(永久)
└─ Web 扫描 404 ──→ 第 1 层 fail2ban nginx-botsearch 封 IP
└─ 第 2 层 auto-ban 每小时封 /24(永久)
```
## 三、Ansible 部署:一条命令推三节点
手动 SSH 到三台机器部署脚本太低效。用 Ansible playbook 一键搞定:
```yaml
- 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 代理 UUID | `config/xray/*.md` | 攻击者可冒充代理节点 |
| 服务器真实 IP | 多个文件 | 绕过 CF 直连源站 |
| 分享链接(含 UUID+路径) | `config/xray/*.md` | 完整代理凭证泄露 |
### 修复方案
NAS 的 git 版本太老不支持 `git filter-branch`,而且仓库只有 4 个 commit——**直接重建历史最干净**:
```bash
# 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 IP | CF 边缘 IP 出现在 nginx 日志 | 脚本排除 CF IP 段 |
## 总结
从昨天手动检查 SSL、手动配 WAF,到今天 Ansible 一键巡检、auto-ban 自动封禁——**运维的本质就是把手动操作变成自动化流程**。
三层防御体系现在完整了:
- **fail2ban**:实时封单 IP,快但不持久
- **auto-ban cron**:每小时封 /24 网段,持久且自动去重
- **Cloudflare WAF**:入口拦截,攻击者摸不到源站
攻击者用自动化脚本扫描,我们就用自动化脚本防御。这不是军备竞赛——**这是把重复劳动交给机器**。
---
*本文已脱敏,不含真实 IP、UUID 或凭证。*