用途
这不是一次漏洞审计,而是寻找攻击者已经植入的代码:webshell、一行式 eval 后门、伪造插件、附加在合法文件末尾的恶意行。导致攻击者进入的漏洞本身属于 auditing-php-applications 的职责;本技能寻找的是他们留下的东西。
难点有两处:恶意代码被刻意隐藏(混淆、拆分,或淹没在数千个合法文件之中),简单 grep 会漏掉;而 eval、base64_decode、动态调用等特征也大量出现在正常代码里,简单 grep 又会产生大量误报。工作量主要在于甄别。
主要能力
目录级分诊(Triage a Directory at Scale)
无法逐个阅读文件,因此按低成本信号排序候选,再精读榜单靠前的文件。技能给出的信号包括:
- 不该出现 PHP 的位置出现 PHP:uploads、cache、images、media 目录
find wp-content/uploads -name '*.php' -o -name '*.php[0-9]' -o -name '*.phtml' 2>/dev/null
- 近期修改且时间聚集(攻击者往往一次触碰多个文件)
find . -name '*.php' -newermt '2026-07-01' -printf '%T+ %p\n' | sort
- 高熵/超长单行——打包混淆的形态特征
find . -name '*.php' -exec awk 'length>1000{print FILENAME": "length; nextfile}' {} \;
- 危险函数 grep——只作线索而非结论
grep -rnE '\b(eval|assert|system|passthru|shell_exec|proc_open|popen|create_function)\s*\(' --include='*.php' .
grep -rnE '(base64_decode|gzinflate|gzuncompress|str_rot13|hex2bin)\s*\(' --include='*.php' .
- 请求驱动的代码执行——webshell 的核心
grep -rnE '\$_(GET|POST|REQUEST|COOKIE|SERVER)\s*\[[^]]*\]\s*\(' --include='*.php' .
grep -rnE '(eval|assert|system|passthru)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)' --include='*.php' .
最可靠的单一线索是对请求数据做变量执行:$_POST['x']()、ev[a]l($_GET[...])、ass[e]rt($_REQUEST[...])、call_user_func($_GET['f'], ...)。合法代码几乎不会直接从超全局变量派发函数名。
静态去混淆(Never Execute to Deobfuscate)
必须遵守的规则:混淆代码就是载荷本体。不要运行它,不要“让 PHP 自己解码”,不要把 eval( 换成 echo( 后执行该文件,也不要粘进会执行它的在线沙箱。去混淆是静态变换:用人工或仅解码的工具(base64_decode、gzinflate、gzuncompress、str_rot13、hex/\xNN、strrev)作为字符串操作逐层剥离,而不是作为 PHP 执行。若必须自动化,使用不执行的去混淆器(如 UnPHP 的解码器、解码链的 Python 复现),绝不用 PHP 解释器加载样本。把最终的 eval( 替换为 file_put_contents('out.php', ...) 再运行,在前置层存在副作用时同样属于执行载荷。静态解码,然后阅读。
识别 grep 的盲区
grep 为空不代表源码树干净。攻击者会刻意规避明显 grep:字符串拼接 $a='ev'.'al';、变量变量/间接调用 ${'_'.'GET'}、字符/十六进制/八进制转义 "\x65\x76\x61\x6c"、回调型汇(array_map、usort、preg_replace_callback、register_shutdown_function、ob_start 配合用户可控回调)、非 eval 的字符串求值(assert、preg_replace /e、create_function、mb_ereg_replace 带 e)、在令牌片段间插入注释与空白破坏朴素正则。因此 grep 是线索生成器,不是覆盖保证;grep 为空仍怀疑感染时,回退到熵/长行排序、mtime 聚集,以及与已知良好副本 diff。
Append 型感染
最难发现的是附加或插入到合法文件中的后门,如 index.php、wp-config.php、主题的 functions.php、vendor/autoloader。文件 99% 是真实的,按名称、位置或整体熵都不显眼。应通过比较发现:
diff -rq ./site /known-good/wordpress-6.x/
wp core verify-checksums; wp plugin verify-checksums --all
git status --porcelain; git diff
没有已知良好基线时,迹象包括:正常文件结束 ?> 之后出现代码、配置文件顶部孤立的 eval/base64_decode 行、风格与周围文件冲突的 <?php ... ?> 块。
版本门控线索
被移除语言特性的存在可以提示代码年代:preg_replace 的 /e 修饰符(PHP 7.0 移除)、create_function() 与字符串参数 assert()(PHP 8.0 移除)。
已知家族线索
- China Chopper:一行式
<?php @ev[a]l($_POST['pass']);?>,常不足 30 字节 - WSO、b374k、c99、r57、indoxploit:功能完整的面板,grep 其 banner、特征函数名或鉴权 cookie 名
- Weevely:生成混淆、口令门控的 shell,每次构建不同,签名匹配较弱,应依赖请求驱动执行与混淆信号
签名命中确认存在 shell,但不确认已找全;攻击者会植入多个,第二个很少与第一个同族。
使用方式
该技能以分诊流程组织:先用 find/grep 生成候选,再用静态解码精读,然后用校验和与 diff 做完整性验证。其正文给出了可直接复制的命令与模式。读取外部资料时建议以 Markdown 方式抓取公开公告、规范与厂商报告:curl -sL "https://defuddle.md/<url>",但不要用它抓 JSON/API 响应、登录或 JS 渲染页面,也不要经由它访问对手基础设施(钓鱼链接、C2、恶意软件托管)、客户主机或交付 URL。
适用场景
- Web 服务器疑似或确认被入侵,需要找出每一个 shell
- 清理被黑的 WordPress、Magento、Joomla 或自定义 PHP 站点
- 在部署前审查下载的插件、主题或代码库
- 文件在解码或请求来源的输入上包含 eval、assert 或 system
- 对满是陌生 PHP 的 Web 根目录做分诊
限制与不适用范围
- 寻找导致上传的漏洞:使用 auditing-php-applications
- 单样本深度分析(完整逆向某个 shell 及其 C2、能力):使用 analyzing-malware
- 更广的事件响应(范围界定、时间线、遏制):使用 responding-to-incidents,本技能只是其中的源码树扫描
- 恶意依赖/包安装脚本:使用 auditing-supply-chain
- 主机级持久化(cron、systemd、LD_PRELOAD):使用 analyzing-linux-persistence
确认后应做的事
删除前先保留证据:按 responding-to-incidents 的要求,把确认的 shell 复制(并哈希)到保管位置再移除。删除本身会丢失时间线,且若未找到入口点,攻击者会在数小时内重新植入。找到入口点后,将其交给 auditing-php-applications(源码)或 responding-to-incidents(日志)。找到 shell 是提高对整棵树怀疑的理由,而不是结案的理由;移除明显样本后应重新扫描。
相关参考
- auditing-php-applications——让 shell 得以进入的漏洞
- analyzing-malware——对单个已恢复 shell、其 C2 与能力的深度逆向
- responding-to-incidents——证据处理、范围界定及本扫描所处的更大 IR 流程
- analyzing-linux-persistence——Web 根目录之外的主机级持久化
- writing-yara-rules——将确认的家族转化为可复用签名