跳转到帖子

背景

最近整理了一起站点被植入 WebShell 的处置记录。事件本身不算复杂,但里面涉及的几个环节比较有代表性:日志回溯、可疑文件定位、入口漏洞判断、代码审计修复,以及后续监控加固。这里把思路整理成一篇长期可参考的复盘,重点放在防守侧排查和修复,不展开攻击利用细节。

场景大致如下:一套 PHP 业务系统部署在 Nginx + PHP-FPM 环境,后端存在文件上传和富文本编辑功能。运维发现服务器出站连接异常,同时 Web 目录下出现了命名不规则的 PHP 文件。业务侧反馈近期没有发布过相关文件。

问题现象

  • Web 根目录及上传目录下出现异常 PHP 文件。
  • Nginx 访问日志中存在对非常规路径的访问,例如上传目录中的 PHP 文件。
  • PHP-FPM 慢日志偶尔出现异常脚本执行记录。
  • 主机侧存在可疑外联行为,但没有发现系统账号被新增。

这类事件最容易误判的地方,是只删除 WebShell 文件就结束。实际处置中更重要的是确认入口,否则文件很可能再次出现。

第一步:保护现场与范围确认

如果业务允许,建议先做隔离或只读快照。至少应保留以下材料:

  • Web 目录完整快照。
  • Nginx / Apache 访问日志、错误日志。
  • PHP-FPM 日志、慢日志。
  • 应用日志、上传记录、管理员操作日志。
  • 最近一次正常版本包或 Git 提交记录。

常用的初步定位命令如下,适合快速找近期变更文件:

find /data/www -type f -mtime -7 -printf '%TY-%Tm-%Td %TH:%TM %p\n' | sort
find /data/www -type f -name '*.php' -mtime -30 -ls
find /data/www/uploads -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.phar' \) -ls

注意不要直接在生产环境上用脚本批量删除可疑文件。先复制样本、记录时间戳和路径,再做处置。

第二步:识别可疑文件特征

WebShell 不一定都有明显的 eval、assert 关键字,但排查时可以先从高风险函数和混淆特征入手:

  • 动态执行:eval、assert、preg_replace /e、create_function。
  • 命令执行:system、exec、shell_exec、passthru、proc_open、popen。
  • 文件操作:file_put_contents、fwrite、move_uploaded_file、unlink。
  • 编码混淆:base64_decode、gzinflate、str_rot13、chr 拼接、大量不可读变量名。
  • 异常入口:使用 $_POST、$_GET、$_COOKIE、php://input 直接进入危险函数。

可以使用 grep 做第一轮筛选:

grep -RIn --include='*.php' -E "eval\s*\(|assert\s*\(|shell_exec\s*\(|system\s*\(|passthru\s*\(|proc_open\s*\(|base64_decode\s*\(|gzinflate\s*\(" /data/www

但不要把命中结果全部视为恶意。很多框架、缓存文件、第三方库也可能出现类似函数。判断时需要结合文件路径、创建时间、代码风格、版本库差异和访问日志。

第三步:日志回溯入口

在确认可疑文件路径后,需要回查它首次出现前后的访问记录。比如可疑文件路径为:

/data/www/uploads/2024/11/a9f3.php

可以在访问日志中查它的请求时间:

grep 'a9f3.php' /var/log/nginx/access.log*

如果存在访问记录,继续向前回溯同一 IP、同一 Cookie、同一 User-Agent 的请求。重点关注:

  • 上传接口,如 /upload、/api/file、/editor/upload。
  • 后台插件、主题、模板编辑功能。
  • 富文本编辑器历史版本上传接口。
  • 反序列化、任意文件写入、路径穿越相关错误日志。
  • 异常状态码组合,例如先 200 上传,再访问上传目录 PHP。

可以按时间窗口筛选:

awk '$4 >= "[20/Nov/2024:10:00:00" && $4 <= "[20/Nov/2024:11:00:00"' /var/log/nginx/access.log > /tmp/window.log
grep '203.0.113.10' /tmp/window.log

这里的 IP 只是示例。实际分析时不要只依赖源 IP,因为代理、云函数、出口 NAT 都会影响判断。更可靠的是组合多个维度:时间、路径、参数模式、认证状态、文件落点。

第四步:针对上传功能做代码审计

在这类事件中,上传功能是最常见入口之一。审计时不要只看前端限制,也不要只检查扩展名。一个相对安全的上传流程至少需要满足:

  • 服务端校验扩展名白名单。
  • 校验 MIME 不能作为唯一依据,只能作为辅助。
  • 对图片类文件做真实解析或重编码。
  • 上传目录禁止脚本执行。
  • 文件名由服务端生成,避免使用用户传入文件名。
  • 限制文件大小、数量和上传频率。
  • 保存路径不能被参数直接控制。

一个常见的不安全写法类似这样:

$filename = $_FILES['file']['name'];
$tmp = $_FILES['file']['tmp_name'];
$path = __DIR__ . '/uploads/' . $filename;
move_uploaded_file($tmp, $path);

问题在于:文件名完全可控,扩展名没有白名单,保存目录位于 Web 可访问路径下。如果 Web 服务器允许解析该目录中的 PHP 文件,就存在较高风险。

改造时可以参考下面的思路:

$allowExt = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$originName = $_FILES['file']['name'];
$tmp = $_FILES['file']['tmp_name'];
$size = $_FILES['file']['size'];

$ext = strtolower(pathinfo($originName, PATHINFO_EXTENSION));
if (!in_array($ext, $allowExt, true)) {
    throw new RuntimeException('invalid file type');
}

if ($size > 5 * 1024 * 1024) {
    throw new RuntimeException('file too large');
}

$newName = bin2hex(random_bytes(16)) . '.' . $ext;
$baseDir = __DIR__ . '/uploads/' . date('Y/m');
if (!is_dir($baseDir)) {
    mkdir($baseDir, 0750, true);
}

$target = $baseDir . '/' . $newName;
if (!move_uploaded_file($tmp, $target)) {
    throw new RuntimeException('upload failed');
}

如果业务只允许图片,建议进一步使用 getimagesize、finfo、图片库重编码等方式验证内容。单纯依赖 Content-Type 很容易被伪造。

第五步:服务器侧禁止上传目录执行脚本

代码层修复之外,Web 服务器配置也要兜底。上传目录原则上不应具备脚本执行能力。以 Nginx 为例,可以对上传目录禁用 PHP 解析:

location ^~ /uploads/ {
    location ~ \.php$ {
        return 403;
    }
}

如果 PHP 解析规则写得过宽,也容易引入风险。应避免让任意路径都进入 PHP-FPM,建议仅对明确的 PHP 文件规则进行处理,并确认 fastcgi_param SCRIPT_FILENAME 指向真实文件。

location ~ \.php$ {
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    fastcgi_pass unix:/run/php/php-fpm.sock;
}

另外,上传目录可以放在 Web 根目录之外,通过后端接口读取并输出,或者使用对象存储配合私有读写策略。这比把用户文件直接暴露在可执行 Web 目录下更稳妥。

第六步:排查持久化与横向影响

WebShell 文件只是表象,处置时还应确认是否存在持久化或进一步入侵痕迹:

  • crontab 是否新增异常任务。
  • 系统用户、SSH key 是否异常。
  • Web 目录是否存在被篡改的框架文件或自动加载文件。
  • 数据库中是否被写入后门配置、计划任务或异常管理员账号。
  • 应用配置文件是否泄露,数据库密码是否需要轮换。
  • 服务器是否存在异常出站连接和未知进程。

常用检查命令:

crontab -l
ls -la /etc/cron.*
last -a
ss -antp
ps aux --sort=-%cpu | head
find /tmp /var/tmp -type f -mtime -7 -ls

如果发现 Web 用户具有过高权限,比如能写入代码目录、能读取敏感配置、能执行系统命令,应尽快收敛权限。PHP-FPM 运行用户不应拥有不必要的系统权限。

第七步:建立复现与修复验证闭环

修复后需要验证入口是否真正关闭。建议在测试环境按同版本代码复现漏洞触发条件,再验证修复效果。验证点包括:

  • 不允许上传脚本扩展名文件。
  • 双扩展名、大小写扩展名、特殊字符文件名均被正确处理。
  • 上传目录中的 PHP 文件无法被 Web 服务器解析。
  • 路径参数无法跳出限定目录。
  • 未登录或低权限用户无法访问高风险上传接口。
  • 日志中能够记录上传者、文件哈希、保存路径和请求 ID。

文件哈希记录在事件回溯中很有用,例如:

sha256sum /data/www/uploads/2024/11/* > /tmp/upload_hash.txt

后续如果再次出现同类文件,可以快速比对是否为同一批样本或变种。

注意点

  • 不要只删文件,不查入口。
  • 不要只依赖 WAF 告警,业务日志和主机日志同样关键。
  • 不要把所有 base64_decode 都视为后门,需要结合上下文判断。
  • 不要在生产环境直接执行不明 PHP 文件做验证。
  • 不要忽略第三方组件版本,历史富文本编辑器和文件管理插件经常是入口。
  • 不要让上传目录、缓存目录、日志目录具备脚本执行能力。

一个简单的排查清单

阶段检查项目的
文件排查近期新增 PHP、可疑函数、上传目录脚本确认异常文件范围
日志分析访问日志、错误日志、应用日志定位首次访问和可能入口
代码审计上传、模板、插件、文件管理功能确认漏洞点
配置加固上传目录禁执行、PHP-FPM 权限降低再次被利用概率
主机排查进程、计划任务、账号、外联确认是否扩大影响
修复验证测试环境复现与回归测试形成闭环

总结

WebShell 事件的处置重点不在于识别某个样本,而在于建立完整链路:发现异常文件、回溯访问日志、确认入口漏洞、修复代码和配置、排查持久化、验证修复有效性。尤其是上传目录脚本执行权限、文件名可控、服务端校验缺失这几个问题,在实际项目中仍然很常见。

比较稳妥的长期做法,是把上传文件当作不可信输入处理:服务端白名单校验,文件重命名,目录不可执行,权限最小化,日志可追踪。这样即使某一层出现疏漏,也不至于直接演变成远程脚本执行风险。

代码审计与漏洞定位示意图
代码审计、调用链与关键函数定位示意

0篇意见

推荐意见

没有意见。

游客
抱歉,你的帖子内容包括我们不允许的字词。请编辑你的帖子,删除下面高亮的屏蔽字。
添加意见…