被扫不是事故,得手才是——烽燧 FengSui

本系列:把这两年用 Python 写的十几个小项目挨个开源、挨个说清楚。不讲”我做了什么”,讲”做的过程中被什么打脸、最后怎么想明白的”。 第 10 篇 · 烽燧 FengSui(从这一篇开始,从游戏转到工具) 源码:github.com/ghostgorge/FengSui 纯 Python 标准库,零第三方依赖,Windows / macOS / Linux,单文件 exe


一、它只回答一个问题

我的站被扫了这么多次,到底有没有出事?

任何一个暴露在公网上的站点,日志里都躺着成千上万条攻击特征。看多了就麻木了——被扫是常态,得手才是事故。

所以这份报告的头号内容不是”今天被扫了多少次”,而是:

哪些命中攻击规则或敏感文件规则的请求,服务器实际返回了 2xx。

这个清单通常只有几条,但每一条都必须有人看过才能翻篇。其余所有统计——TOP IP、UA 分布、404 比例、时间曲线——全部是给这几条做背景的。

一个安全报告的价值,不在于它列出了多少东西,而在于它敢不敢告诉你”这几条你必须看,其余可以不看”

二、为什么又造一个轮子

方案缺口
GoAccess强在流量统计(PV/UV/带宽/地域),不是安全审计;Windows 上要 WSL
微软 Log Parser Studio依赖 Log Parser 2.2,十年没更新,本质是给你一个 SQL 查询框,规则得自己写
ELK / SIEM能力强得多,但要部署一整套,不是”拿到一份日志想现在看一眼”的工具
云 WAF 面板只看得到过 WAF 的流量,且往往拿不到原始日志
grep + 眼睛能用,但敏感路径几十条、攻击特征几十种,靠记忆必然漏

空白在中间:一个免安装、拿到日志就能跑、直接给结论的单文件工具。

三、这个项目的真正难点是降噪

写规则匹配不难,32 条攻击签名、26 条敏感路径,一天能写完。难的是让报告在真实环境里仍然可读

一份把所有可疑请求都列出来的报告,和没有报告是一回事——没人会看第二遍

下面这几条,每一条都是从”这报告没法看”倒推出来的。

1. 敏感路径必须分三档

档位含义返回 200 时
fatal.env.git/config、私钥、备份文件、heapdump记为得手,等同凭据泄露
normalDruid / Tomcat 管理台、phpinfo、Nacos记为得手,需要人看
info/admin、phpMyAdmin、/wp-login.php、Swagger不算得手

第三档是必须有的。 没有它,每一个正常登录后台的同事都会被报成入侵者。

/admin 返回 200 是完全正常的事——那是你们自己的后台。把它算成”得手”,报告的第一页就全是自己人。

2. 工具指纹要分强弱

  • 强指纹sqlmapniktonuclei——除了攻击没别的用途,可以直接定罪
  • 弱指纹python-requestscurlGo-http-client——公司自己的巡检脚本大量在用,只作佐证,不单独定罪

3. 目录枚举要看”落点分散度”

这条是我最满意的一个判据。

只看”很多不同路径都 404″,会把网站改版后的死链、过期的 sitemap 全打成攻击。但它们有个共同特征:404 全都挤在同一个一级目录下/news/xxx/news/yyy……)。

而真正的目录枚举是拿字典撞根目录,落点必然散在很多个一级段上/admin/backup.zip/.git/config.php……)。

加一条”一级路径段数量”的门槛,几乎零成本换掉一大类误报

4. 404 的登录路径不算爆破

/wp-login.php 连打 200 次但全是 404——那不是爆破,那是目录扫描(这站根本没装 WordPress)。

不加这个条件,任何字典里带 wp-login.php 的扫描器都会被误判成爆破

5. “内网”只认 RFC1918

这条值得单独说,因为它是个真陷阱:

Python 的 ipaddress.is_private 会把 100.64.0.0/10 也算作私有地址。

那是运营商级 NAT(CGNAT)网段。国内大量真实用户和真实攻击者就是从这个段出来的——跟着标准库走,等于把一批真攻击者直接洗白成”内网访问”。

文档示例段(192.0.2.0/24 等)同理。

所以内网判定是自己写的,只认 RFC1918 那三段。

更一般的教训:标准库的语义是按 RFC 定义的,不是按你的业务场景定义的。 is_private 回答的是”这个地址是否不可在公网路由”,不是”这是不是自己人”。

四、两个工程决定

内存不随日志行数增长

逐行读、读完就丢,只往按 IP 聚合的画像里累加计数。

IP 数有硬上限(默认 20 万),到顶后新 IP 只计总数不再建档,并在报告里写明

宁可少给一部分明细,也不能因为内存爆掉、整份报告出不来。

自测里有一项用 tracemalloc 实测:行数 ×10,峰值内存 ×1.00。

静态资源照样跑规则,只是不计入行为统计

/theme/style.css?id=1 union select ... 当然也是注入,规则必须跑。

但页面里几十张图片 404,会把任何一个普通访客的 404 占比顶到 90%。所以静态资源不进路径集合、不计 404 比例

“要不要检测”和”要不要计入统计”是两个独立的开关——这个区分在很多日志工具里是混在一起的。

五、71 项自测,其中一半是误报测试

这个数字是这篇文章我最想让人看到的一行。

对一个规则匹配工具来说,”能不能检出攻击”是容易测的——构造一条 sqlmap 的请求扔进去,看它报不报。

难的是另一半:构造一堆完全正常的流量,看它闭不闭嘴。

  • 正常同事登录 /admin → 不能报得手
  • 网站改版后一堆 /news/* 死链 → 不能报目录枚举
  • 公司巡检脚本用 python-requests → 不能单独定罪
  • 100.64.x.x 来的正常用户 → 不能被当成内网洗白,也不能被当成攻击者

检出率测试证明工具有用,误报测试才证明工具能用。

六、一个只在 Windows 上会踩的打包坑

产物有两个 exe:FengSui.exe(窗口版)和 fengsui-cli.exe(命令行版)。

这两个名字不能只差大小写。

Windows 文件系统大小写不敏感,FengSui.exefengsui.exe 是同一个文件——PyInstaller 会先建一个,再把另一个覆盖上去,你拿到的两个 exe 其实是同一个程序。

在 Linux 上大小写敏感,这个错根本测不出来。所以命令行版的名字里必须带 -cli

七、上手

窗口版:双击 FengSui.exe → 选日志目录 → 开始侦察。双击表格里任意一行,看该 IP 的请求样本。

命令行版

rem 第一步永远是 peek:先确认日志格式能被正确读懂
fengsui-cli peek C:\inetpub\logs\LogFiles\W3SVC1

rem 出报告
fengsui-cli scan C:\inetpub\logs\LogFiles\W3SVC1 --html 报告.html

rem 只要结论,接计划任务
fengsui-cli scan /var/log/nginx --quiet --no-report --json r.json
if errorlevel 1 echo 发现得手请求

rem 导封禁清单
fengsui-cli scan /var/log/nginx --blocklist deny.conf --blocklist-style nginx

退出码:0 未发现得手 · 1 发现得手请求 · 2 出错。这一条是为了能直接挂进计划任务或 CI

格式自动嗅探,不用指定:IIS W3C(按 #Fields:动态取字段,不写死列序;UA 里的 + 会还原成空格)、Nginx/Apache combined、Apache common。.gz 直接读不用解压,目录可以直接给、会递归找。

peek 这个子命令是刻意做的:任何日志分析工具,第一步都应该是”让人确认它读懂了”。字段错位的解析结果比没有结果更危险。

报告是单文件、无外链——双击能看,也能当附件直接发给领导。CSV 一律带 UTF-8 BOM,Excel 打开中文不乱码。

八、已知边界(照实写)

  • 只看 Web 访问日志。 不做 syslog、防火墙日志、系统日志。日志只能证明”被记录下来的事”,不覆盖不经 Web 层的入侵路径,也不覆盖日志被清理之后的时间段
  • 基于规则匹配,既会漏报也会误报。 每条结论都附了原始请求,请以原始日志为准。这个工具的定位是”把该看的几条挑出来给人看”,不是替人下结论。
  • 不做实时监控、不常驻、不装服务、不拦截。 跑完就退出。
  • 不做 GeoIP(要外部数据库)、不做反查 DNS(慢,且会把攻击者的域名查询泄露出去)。
  • 封禁清单默认排除内网地址和已识别爬虫——把自己的监控或办公网段封掉,是这类工具最常见的事故。
  • 单次分析的”得手”证据上限 500 条,超出只计数不留样本。
python selftest.py          # 71 项自测,全绿再往下走
python main.py              # 窗口版
双击 build.bat              # 先跑自测,通过才打包,最后校验产物体积

尾巴

这个项目和前面九个游戏最大的区别,是它的”难度”不在算法上——规则匹配一天就能写完。它的全部工程量在降噪

一条带得走的:

安全工具的可用性,取决于它敢不敢闭嘴。

一个把所有可疑项都列出来的工具,等于把判断责任原封不动推回给人;而人看两次就不看了。真正有用的做法是分档——哪几条必须现在看,哪几条只是背景噪声,工具必须自己承担这个划分。

以及那条更小但更实用的:标准库的语义是按 RFC 写的,不是按你的场景写的。 is_private 把运营商 NAT 算成私有地址,这在别的场景下完全正确,在这里就是致命的。

📦 源码https://github.com/ghostgorge/FengSui


下一篇:核痕 KeHen——目录快照与核对工具。回复「继续」。