你的 Web 服务器日志早已知道你是否遭到攻击。weblogchecker 会读取它们。
phpscan 告诉你你的 PHP 是否暴露。phpscan hunt 在 webroot 中搜索 shell。weblogchecker 回答第三个问题——是否真的有人使用了它们?——依据的是大多数站点已经拥有却无人阅读的唯一记录:访问日志。
它流式读取你拥有的 Apache 和 nginx 访问日志,找出真正重要的交互——webshell 响应请求、以重定向告终的暴力破解突发、获得 200 响应的漏洞利用模式——并报告每一项,附带严重性、置信度、作为证据的实际日志行,以及应对措施。
weblogchecker analyze /var/log/apache2/access.log
退出码:0 = 未发现指标,1 = 发现指标,2 = 用法错误。
添加 --format json 可输出机器可读格式,添加 --since 2026-10-01 可
限制时间窗口。.gz 日志会被直接读取;- 表示从标准输入读取。
0.2.0 新增功能 — 基线差异对比。 签名列表只认识那些 已经有名字的 shell。而落到你的站点上的那个 shell 却有一个随机名字。 给 weblogchecker 提供同一站点的一份可信的较早日志,它就会报告 那些此前从未出现过的可执行文件:
weblogchecker analyze access.log --baseline access.log.last-month
每个首次出现的可执行路径(.php、.phtml、.cgi、.asp、.jsp……)仅依据日志中的五个弱信号进行评分——返回 2xx/3xx、使用了 POST/PUT、无 referrer、被至多 2 个 IP 访问、位于 upload/cache/temp 目录下——并以 NEW 形式报告,附带信号、计数和日志行作为证据。仅首次出现属于低严重性提示;严重性仅在信号叠加时才上升(详见下文),因此你上周安装的插件不会被判定为安全事件。
一行命令,从 PyPI 安装(Python 3.11+,仅使用标准库——零依赖):
pip install weblogchecker
首个 PyPI 发布待定。 在 PyPI 可信发布者配置完成之前,请从 GitHub 安装完全相同的代码:
pip install git+https://github.com/PHPatch/weblogchecker.git(或下载下方已签名的发布压缩包)。一旦首个 PyPI 发布上线,上方的pip install weblogchecker命令即可使用。
在拒绝并提示 "externally-managed-environment" 的系统上 (Debian/Ubuntu 和 Homebrew 的 Python),请改用 pipx:
python3 -m pip install --user pipx # if you don't have pipx yet
pipx install weblogchecker
已签名的发布包(sdist + wheel + PGP 签名 + SBOM)位于
GitHub releases 页面。
使用 pgp-key.asc 中的密钥验证签名——指纹为
BAA1 586A AD37 18C0 CE41 D288 FE23 A624 54E3 5307。参见 SECURITY.md。
| 日志中的模式 | 严重性 / 置信度 | 为何重要 |
|---|---|---|
| 已知 webshell(c99、r57、FilesMan、b374k、WSO)返回 2xx | 高 / 高 | 该 shell 存在并处理了请求 |
| 相同名称被探测但返回 404/403 | 低 / 低 | 有人在查找;什么也没找到——仅为上下文,不是警报 |
eval-stdin.php(phpunit)有响应 | 高 / 高 | 来自 2026 年 10 月 php2shell 浪潮的 UnitTest shell 执行了 POST 的代码 |
/uploads/、/media/、/files/… 内的可执行文件(.php 等)有响应,尤其是 POST | 高 / 高 | 上传目录绝不应执行代码 |
| 对静态文件(.jpg、.css…)的 POST/PUT 返回 2xx | 高 / 中 | 浏览器不会向图片发送 POST;这个“图片”往往是脚本 |
URI 中的漏洞利用模式(路径遍历、eval(、php://、长 base64 令牌)返回 2xx | 高 / 中 | 该尝试可能已成功 |
| 相同模式被拦截 / 返回 404 | 低 / 低 | 背景噪声;仅作为活动上下文有用 |
| 对 wp-login.php / xmlrpc.php 的暴力破解突发(≥20 次 POST / 10 分钟) | 中 / 中 | 单一 IP,非人类速度 |
| 该突发中在多次失败后出现 302 | 高 / 中 | 正确密码的特征——可能已被接管 |
| 扫描器 user-agent(sqlmap、nuclei、nikto…) | 低 / 低 | 仅供参考;扫描不等于入侵 |
可执行路径不在你的 --baseline 日志中,却有请求响应 | 低 → 高 / ≤ 中 | 任何黑名单都不认识的未知名称 shell;严重性随 POST、缺失 referrer、少量 IP、上传目录而叠加 |
每个发现类别都附带修复建议:隔离文件、轮换凭据、审查该 IP 触及的一切。Webshell 发现会指向配套工具——phpscan hunt(pip install phpscan,
github.com/PHPatch/phpscan)——它会扫描 webroot 本身,查找这些日志模式所指向的 shell。
本工具与 phpscan 遵循同一信条构建:
解析采用行流式处理:文件从不加载到内存中,聚合时每个发现最多保留五行证据。在本项目的 fixture 生成器上实测(366,306 行、72 MB 的真实组合日志):在普通虚拟机上为 7.6 秒,峰值 RSS 12.8 MB。2 GB 的日志占用相同内存,耗时按比例增加。
$ weblogchecker analyze access.log
weblogchecker 0.2.0 - access-log compromise analysis
Log: access.log
Lines read: 13 | parsed: 13 | unparseable (skipped): 0
Indicators found. These are leads for investigation, not proof of
compromise: work through the findings in severity order ...
SUMMARY: 10 findings (4 high, 1 medium, 5 low) across 9 incidents, 2 IPs flagged
INCIDENT TIMELINE (grouped by IP + path):
[HIGH] 198.51.100.66 /wp-content/uploads/2026/10/c99.php
classes: webshell-access, exec-in-upload-dir | requests: 6 | ...
...
1. [high severity / high confidence] Known webshell filename answered a request
198.51.100.66 /wp-content/uploads/2026/10/c99.php - 3 request(s), ...
| 198.51.100.66 - - [28/Oct/2026:03:13:40 +0000] "GET /wp-content/..." 200 ...
Remediation: Treat the named file as a live webshell until you have
proven otherwise. Locate it in the webroot and quarantine it ...
hunt 查找磁盘上的 webshell)全部免费、全部开源,且都不会发送漏洞利用探测请求。
MIT — 见 LICENSE。安全报告:见 SECURITY.md。