peaceharborco/swatter
GitHub: peaceharborco/swatter
Swatter 是一个基于 Bash/awk 的轻量级 Web 日志分析工具,能感知 Cloudflare 并在正确的防火墙层面自动封禁恶意 IP。
Stars: 1 | Forks: 0
# 🪰 Swatter
[](https://github.com/peaceharborco/swatter/actions/workflows/ci.yml)
[](LICENSE)
**一个感知 Cloudflare 的滥用行为击退工具,专为 cPanel + CSF 服务器设计。**
Swatter 会读取您的 Web 服务器日志,根据加权的行为信号加上可选的威胁情报对每个 IP 进行评分,
并自动在正确的防火墙层面上封禁恶意 IP。分布式扫描器、凭据暴力破解、
`/.env` 和 `/.git` 探测、漏洞模糊测试工具以及请求洪水,在导致您的负载飙升之前就会被击退。
屡教不改者将面临永久封禁。
它作为一组小型的 Bash + awk 脚本在 cron 上运行。没有 agent,没有 daemon,没有
编译依赖,也没有任何外联通讯。
```
$ swatter top
IP SCORE OFFN TEMP PERM CHANNEL LAST
45.146.165.10 92 4 3 1 csf high_badpath_repeat
104.152.52.20 78 1 1 0 csf scanner_profile
193.32.162.40 85 2 2 0 cloudflare request_flood
```
## 为什么它与众不同:它不会搞垮您的网站
大多数“针对 Apache 的 fail2ban”工具在由 Cloudflare 前置的服务器上都有一个致命的盲点。
您的日志显示了**真实的访客 IP**(通过 `mod_remoteip` /
`CF-Connecting-IP`),但实际的 TCP socket 是一个 **Cloudflare 边缘 IP**。
在您的服务器防火墙上封禁该 socket,您就等于封禁了 Cloudflare —
**该机器上的所有网站都会宕机。**
Swatter 正是围绕这一点构建的。对于每个违规者,它会做出以下决定:
- **直连源站**(访问了您的原始 IP 或 cPanel 服务端口,*或者*从非 Cloudflare 节点
针对您的 Web 端口保持了活跃的 TCP 连接 — 即使在持有有效的 `Host` 头的情况下也是如此,因为代理请求总是从 CF 边缘到达)
→ 在 **CSF** 处进行封禁。这是安全的:该 socket 确实属于攻击者。
- **通过 Cloudflare**(经由代理进入) → 通过 API 应用区域范围内的 **Cloudflare IP Access Rule**,
默认为 **managed challenge** — 因此误报意味着人类用户需要解决一个验证,而不是被锁定。
CSF 层面永远不会被触碰。
- **模糊不清** → 默认使用 Cloudflare 层面(更安全的那个)。
Cloudflare 自身的 IP 段被硬编码为**永不封禁**集合,并在每次执行封禁前立即重新检查。
如果 IP 段列表丢失或过期,Swatter 会**安全失效** — 它会完全停止下发 CSF 拒绝规则,而不是
冒着服务中断的风险。
四种 Cloudflare 防御姿态(`CF_MODE`):
| 姿态 | 您的设置 | 经由 CF 的违规者 | 直连违规者 |
|---|---|---|---|
| `auto` | **默认** — 检测并适应 | 除非证实为裸露直连,否则使用安全层面 | CSF |
| `direct` | 位于 CF 之后,Swatter 是防御主力 | 通过 API 进行验证/封禁 | CSF |
| `skip` | 位于 CF 之后,**由您**运行自己的 WAF 规则栈 | 记录日志,交由您的边缘规则处理 | CSF |
| `off` | 根本不在 Cloudflare 后面 | 不适用 — 全都是直连 | CSF |
**开箱即用的二选一。** `auto`(默认模式)会检测服务器是否
位于 Cloudflare 之后 — 它会解析您自己的 vhost 并检查它们、您的
入站 socket 以及您的 Web 服务器配置是否与 Cloudflare IP 段匹配 — 然后
为您选择防御姿态。检测在安装、`refresh-feeds` 和
`test-config` 时运行,绝不会在每次扫描时运行,因此 cron 路径的成本很低。它是**偏向安全**的:
只有明确判定“这台机器不在 Cloudflare 后面”时才会关闭
CF 层面;任何不确定的情况都会保持分类功能开启(即安全层面),所以
`auto` 永远不会错误地将代理 socket 导向 CSF。因此,一台**完全没有 Cloudflare 域名**的机器
会作为一个纯粹的 CSF 自动封禁器运行,无需任何 Cloudflare 配置 —
而且它确实会下发封禁:安全失效机制(当 CF IP 段列表丢失时会禁用 CSF 拒绝)只会在由 CF 前置的机器上触发,
因为在这种机器上才需要通过列表来区分边缘 socket 和直连 socket。
检测每天重新运行一次(即 `refresh-feeds` cron),因此如果某台机器
加入或离开 Cloudflare,`auto` 也会做出适应。它从
domlog 文件名中识别该机器的域名,因此在**纯粹的(非 cPanel)Apache/nginx** 服务器上,如果只有一个共享的访问日志,
它无法证明该机器是裸露直连的,并会保持在安全的、开启分类的防御姿态。在这样一台不在 Cloudflare 后面的服务器上,
请显式设置 `CF_MODE="off"`(`test-config` 会告诉您它何时无法自行判断)。
如果您维护自己的 Cloudflare WAF 规则、速率限制和登录页面
保护,请显式设置 `skip`:Swatter 会避开您已经管理的领域,但仍然会在 CSF 处击退扫描原始 IP 的扫描器,并记录下它本应执行验证的内容。
**不要**在代理服务器上使用 `off` 来表示“不对 Cloudflare 进行干预” — `off` 会禁用层面分类本身,因此代理访客(包括您自己的客户)
可能会被错误地导向 CSF 拒绝规则,这不仅会阻断他们,还会切断他们的邮件和 cPanel 访问权限。(设置任何显式模式都会跳过检测。)
在理念上最接近的工具是 [CrowdSec](https://crowdsec.net) 及其
Cloudflare bouncer,它很好用 — 如果您想要一个 daemon、一个中心化信誉
网络以及针对每个关注点的独立组件。Swatter 则处于光谱的另一端:
bash + gawk + cron,没有常驻进程,cPanel/CSF 原生,并且
将直连与代理的分类内建到每一个决策中,而不是作为 bouncer 事后附加。
## 评分机制
每个 IP 都会在一个滑动窗口内,根据信号的**加权混合**获得 0–100 的评分:
| 信号 | 捕获目标 |
|---|---|
| 请求速率 | 高频敲击 / 洪水攻击 |
| 错误率 (4xx/5xx) | 扫描器会产生错误 |
| 错误爆发 (403/404/444) | 暴力破解 & 路径模糊测试 |
| URL 扇出 | 路径枚举 / 扫描 |
| **恶意路径命中** | `/.env`, `/.git`, `wp-login.php`, `xmlrpc.php`, `/cgi-bin/`, shell 投放, … |
| User-agent | `sqlmap`, `nikto`, `zgrab`, 空的 UAs |
| POST 洪水 | 登录 / xmlrpc / 垃圾评论 |
| 无 vhost / 裸 IP | 只有攻击者才会通过 IP 直接访问您 |
| **信誉度** | AbuseIPDB, Spamhaus, IPsum (可选) |
单纯的平均值会淡化*针对性*的攻击(凭据暴力破解只会触发少数
信号),因此 Swatter 将混合评分与**决定性下限**配对:
`/.env` 探测、持续的洪水攻击、广泛的扫描器特征,或对
敏感端点的重复命中,每一项都足以独立触发行动 — 而且每个决策都会将*原因*连同证据一起记录到
`/var/log/swatter/decisions.jsonl`。
可以在 `/etc/swatter/swatter.conf` 和 `/etc/swatter/badpaths.conf` 中调整所有内容(权重、阈值、恶意路径表)。
## 威胁情报富化(可选,免费)
对于看起来已经形迹可疑的 IP,Swatter 可以在采取行动前对照外部
信誉源进行交叉验证:
- **IPsum** — 聚合的阻止列表,不需要密钥。
- **Spamhaus DROP/EDROP** — 被劫持/犯罪的网段,不需要密钥。
- **AbuseIPDB** — 置信度评分,免费层级(每天 1,000 次检查),支持缓存并
有配额限制。在 `/etc/swatter/swatter.conf` 中设置 `ABUSEIPDB_KEY` 即可启用它。
信誉度只会在分数处于边缘时将其*提高* — 它永远不会单独执行封禁,并且
失败或离线的查询会被直接忽略。在 API 密钥数为**零**的情况下也能完全工作。
## 每日摘要 — 击退错误*以及*恶意行为者
`swatter report` 会每晚发送一封摘要邮件,涵盖服务器健康的各个层面:
- **恶意行为者** — 执行的封禁(永久/临时),按违规类型、恶意路径
类别和渠道(CSF vs Cloudflare)分组,外加供审查的允许列表豁免情况。
- **服务器错误** *(可选,`ERROR_DIGEST_ENABLE`)* — 来自 Apache、
PHP-FPM、各站点 PHP 和 MySQL 在同一时间窗口内的 FATAL/ERROR,过滤掉了已知的大量
噪音,其余部分按特征分组。可以直接指向日志,或者
复用现有的统一错误日志。
在真正安静的时段它会保持静默。发送方式是可插拔的 —
`sendmail`(默认)、**SendGrid** 或 **Brevo** — 因此那些 IP 不是 From 域名授权发件人的主机
仍然可以进行发送。安装程序会安排它每晚运行;请根据您的时区设置 cron 小时。
```
swatter report --test # force-send now, to verify delivery
swatter report 7d # ad-hoc 7-day digest
swatter report --print # print the digest to stdout, send nothing
```
### 邮件发送设置
所有的控制开关都在 `/etc/swatter/swatter.conf` 中:
1. 设置 `REPORT_EMAIL`(留空则禁用摘要)和 `REPORT_FROM`。
2. 选择 `REPORT_METHOD`。`sendmail` 不需要其他设置。`sendgrid` 和 `brevo`
需要 `curl` + `jq` 以及一个 API 密钥 — 将其保存在仅限 root 访问的文件中,并让配置指向它:
install -m 600 /dev/null /etc/swatter/sendgrid.key
vi /etc/swatter/sendgrid.key # 粘贴 API 密钥,不要写其他内容
然后在 `swatter.conf` 中:`REPORT_METHOD="sendgrid"` +
`SENDGRID_KEY_FILE="/etc/swatter/sendgrid.key"`,或者
`REPORT_METHOD="brevo"` + `BREVO_KEY_FILE="/etc/swatter/brevo.key"`。
3. 使用 `swatter report --test` 验证发送情况。
## 安全第一
- **默认仅生成报告。** 开箱即用时,Swatter 只会评分并记录决策,但
不会触碰任何东西。观察它一周,然后将 `SWATTER_MODE` 切换为 `"enforce"`。
- **先临时后永久。** 第一次违规是临时封禁(TTL 阶梯为
1h → 6h → 24h → 72h)。永久封禁是通过屡次违规*赢得*的,绝不会因
单个时间窗口而触发 — 因此一次异常的爆发不会导致共享/CGNAT IP 被拉黑。
- **熔断器。** 对每次运行执行的封禁数量有硬性上限,且针对
灾难性的 CSF 渠道有一个单独的、更低的上限。日志解析 bug 不会导致数千个 IP 被封。
- **永不封禁的允许列表**,最后检查:Cloudflare IP 段、您的 `csf.allow`、
服务器自身的 IP、RFC1918、您的运维人员 IP(`swatter allow `)、
监控服务,以及**正向确认的**优秀爬虫(Googlebot/Bingbot 通过反向 + 正向 DNS 验证,因为仅靠 PTR 是可以伪造的)。
- **敏感路径需要失败证据。** 访问 `wp-login.php` 不是
罪过 — 您的客户每天都在这样做。暴力破解下限只会在
*重复的失败*尝试(错误或 POST 洪水)时触发,因此登录并在
wp-admin 中工作的网站所有者绝不会因此而被封禁。
- **完全审计 + 申诉。** `swatter why ` 会确切显示触发封禁的原因;
`swatter unblock [--perm-allow]` 会在两个层面上同时撤销封禁。
## 安装
要求:一台 cPanel/Apache + **CSF** 服务器 (AlmaLinux/CentOS/RHEL),`gawk`,
`flock`。可选:`jq` + `curl`(威胁情报 & Cloudflare API),`sqlite3`
(如果没有,则会回退到普通文件)。
```
git clone https://github.com/peaceharborco/swatter.git
cd swatter
# 在服务器本身上(以 root 身份):
sudo ./install/install.sh local
# …或者通过 SSH 从你的工作站推送:
./install/install.sh remote root@your-server
```
然后:
```
swatter refresh-feeds # pull Cloudflare ranges + intel feeds
swatter test-config # sanity-check deps, paths, CF token
swatter scan --dry-run # see what it WOULD do
swatter top # review the worst offenders
```
**在您强制执行之前:教会它您是谁。** 在报告模式下运行一周,并
查看它*原本会*封禁哪些内容(`swatter top`、`swatter why `、
每日摘要)。该列表中实际上是您自己、您的员工或
客户的任何 IP,在第一次强制执行之前,都应该被加入永不封禁集合:
```
swatter allow 203.0.113.7 "office"
swatter allow 198.51.100.0/24 "client X static range"
```
任何自动封禁工具最令人尴尬的失败就是封禁客户 —
一个输错密码然后在 wp-admin 中操作的网站所有者,看起来可能非常
像一次攻击。Swatter 的评分机制旨在区分这两者,但您的
运维人员 IP 值得拥有绝对的保证,而不仅仅是一个概率。
当您信任它时,在 `/etc/swatter/swatter.conf` 中设置 `SWATTER_MODE="enforce"`。
安装程序会添加一个 cron 条目,每 5 分钟扫描一次,并每天刷新情报源。
### 启用 Cloudflare 层面
要在 Cloudflare 处封禁代理攻击者(不仅仅是 CSF 直连攻击者),Swatter
需要为每个 CF 账户提供一个令牌,其权限范围**仅限**
`Firewall Services: Edit`(区域 IP Access Rule — 这与 WAF Rulesets 是不同的产品,因此它永远不会与
您现有的 WAF 自动化发生冲突)。它会在攻击者命中的特定区域内封禁他们,
使用的是您的域名→账户映射。
1. 在 Cloudflare 仪表板中,为每个账户创建一个 API 令牌,仅包含针对该账户所在区域的单一权限
*Zone → Firewall Services → Edit*。
2. 构建一个仅限 root 访问的凭证文件(格式为 `accounttoken`,每个账户一行)以及一个
`cf-domains.conf` 风格的域名列表,然后从您的工作站部署这两者 —
不会提交任何密钥,并且只有权限最小的令牌会进入服务器:
./install/swatter-deploy-cf-creds.sh \
--creds /path/to/cloudflare.creds \
--domains /path/to/cf-domains.conf \
--host root@your-server
这会写入 `/etc/swatter/cloudflare.creds` (0600) 和
`/etc/swatter/cf-domains.map` (0644),然后运行 `test-config`。接着,预演模式将会
为代理违规者显示 `cloudflare block in `。(不在
Cloudflare 后面的主机可以完全跳过所有这些步骤 — 默认的
`CF_MODE="auto"` 会检测到裸露直连的机器并作为纯粹的 CSF 自动封禁器运行;设置
`CF_MODE="off"` 可以显式声明这一点并跳过检测。)
### 推荐:使 Cloudflare 分类绝对精确
默认情况下,Swatter 会从您的日志*以及*活跃的源站
socket 中推断是直连还是代理 — 在您的 Web 端口(`DIRECT_WEB_PORTS`,默认为 `80 443`)上的 TCP 对端,如果
不是 Cloudflare 边缘,那就可以证明是直连,这可以捕获绕过
Cloudflare 并带有有效 `Host` 头的正在进行中的洪水攻击。如果要在基于日志的事实判定中也做到精确,
请将 Cloudflare 的 ray ID 添加到您的 Apache 日志格式中 — 存在意味着
请求是通过 Cloudflare 经由代理传入的,不存在则意味着是直连:
```
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" cfray=%{CF-Ray}i" swatter
```
(这是可选的 — 没有它,启发式算法也能工作。)
### 与您的边缘 WAF 的分工
如果您已经在 Cloudflare 边缘运行了严密的保护 — 比如在 `wp-login.php` 和 `wp-admin` 上设置了
受地理限制的 managed challenge — 想一想这会对 Swatter 在源站看到的流量产生什么影响:
**这些路径上的恶意噪音根本就不会到达。** 剩下敲击您登录页面的流量
绝大多数是合法的(网站所有者和通过了您设置的验证的人),因此
源站基于路径的怀疑就会发生反转 — 它开始*挑选出*您的
客户,而不是抵御攻击者。
在这种设置下有两种运行方式:
- **`CF_MODE="skip"`** — 最清晰的划分。您的边缘规则拥有代理
领域;Swatter 负责拥有边缘永远看不到的东西:来自
找到了您服务器真实 IP 的扫描器的直连源站流量。在那个层面上,恶意路径表
处于全功率状态,因为没有合法用户会通过您的原始
服务器 IP 登录 WordPress。(在这里请使用 `skip`,而**不是** `off`:这台机器*确实*位于 Cloudflare 之后,所以
分类必须保持开启,否则代理 socket 可能会被 CSF 拒绝。`skip`
保持分类开启,并简单地将经由 CF 的层面交由您的边缘规则处理。)
- **保留 CF 层面,但在 `config/badpaths.conf` 中降低认证路径的权重**
(例如将 `wp-login.php` 从 HIGH 降为 MEDIUM),如果您的边缘保护是部分性的 — 比如
一个仍然允许同国家机器人进入的地理验证。
无论哪种方式,暴力破解下限都要求有失败的证据(失败的 POST 请求 /
错误响应),因此即使是默认的调试规则,也不会因为网站所有者登录而封禁他们。
## 命令
| 命令 | 用途 |
|---|---|
| `swatter scan [--dry-run\|--enforce]` | 摄入 → 评分 → 决策 → 执行 |
| `swatter status` | 状态、模式、允许列表健康状况、计数 |
| `swatter top [-n N]` | 最严重的跟踪违规者 |
| `swatter why ` | 封禁背后的证据和历史 |
| `swatter allow [note]` | 添加到永不封禁集合(涵盖两个层面) |
| `swatter unblock [--perm-allow]` | 在两个层面上撤销封禁 |
| `swatter list [temp\|perm\|cf\|allow]` | 当前的封禁 / 允许列表 |
| `swatter report [WINDOW] [--test\|--print]` | 发送按违规和操作分组的摘要邮件 (`--print`: 仅输出到标准输出) |
| `swatter refresh-feeds` | 更新 Cloudflare IP 段 + 情报源 |
| `swatter test-config` | 验证配置和依赖项 |
## 运作机制
```
logs ─▶ ingest ─▶ score.awk ─▶ [past WATCH?] ─▶ threat-intel ─▶ classify ─┬▶ CSF (direct)
(domlogs, (weighted (per-IP (AbuseIPDB/ (direct └▶ Cloudflare WAF (proxied)
access_log) signals + evidence) Spamhaus/ vs CF)
decisive IPsum) │
floors) ▼
never-block check (last)
+ circuit breakers
│
▼
SQLite ledger + decisions.jsonl
```
增量日志读取使用基于单个文件的字节游标(即使是最繁忙的日志也能精准处理,
在日志轮转中依然存活)。一切都在 `set -uo pipefail` 下运行,通过 `flock` 保证单实例运行,
并使用 awk 一次性完成解析 — 它可以扩展以应对高并发的访问高峰期,而无需为每一行进行 fork 操作。
## 许可证
MIT © [Peace Harbor Studios](https://studios.peaceharbor.com)。详见 [LICENSE](LICENSE)。
欢迎贡献 — 新的防火墙后端(ipset/nftables)、一个 nginx 日志
解析器,以及额外的情报提供商,都可以无缝接入现有的适配器
接口。
标签:Web安全, 入侵防御, 威胁情报, 密码管理, 应用安全, 开发者工具, 数字取证, 自动化脚本, 蓝队分析, 运维工具, 防火墙策略