本系列:把这两年用 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 | 记为得手,等同凭据泄露 |
normal | Druid / Tomcat 管理台、phpinfo、Nacos | 记为得手,需要人看 |
info | /admin、phpMyAdmin、/wp-login.php、Swagger | 不算得手 |
第三档是必须有的。 没有它,每一个正常登录后台的同事都会被报成入侵者。
/admin 返回 200 是完全正常的事——那是你们自己的后台。把它算成”得手”,报告的第一页就全是自己人。
2. 工具指纹要分强弱
- 强指纹:
sqlmap、nikto、nuclei——除了攻击没别的用途,可以直接定罪 - 弱指纹:
python-requests、curl、Go-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.exe 和 fengsui.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——目录快照与核对工具。回复「继续」。
