跳转到帖子

背景

WebShell 排查是很多应急场景里绕不开的一环。相比“发现一个可疑文件就结束”,更有价值的是把入口、落点、执行链路和处置措施串起来,否则很容易出现删了文件但攻击者很快又回来。

这篇文章整理一次常见 Web 站点被写入脚本后门的排查思路,重点放在日志关联、文件取证、代码特征识别和加固复盘。内容只讨论防御和分析方法,不涉及攻击利用细节。

问题场景

某业务反馈站点偶发异常,表现为部分接口响应慢,且服务器 CPU 在短时间内有异常抖动。初步查看进程没有发现明显异常服务,但 Web 目录下存在近期新增的 PHP 文件,命名类似正常资源文件。

环境大致如下:

  • Nginx + PHP-FPM
  • 业务代码部署在 /data/www/app
  • 访问日志位于 /var/log/nginx/access.log
  • 应用存在文件上传功能

这种场景下,不建议直接删除可疑文件后收工。更合理的做法是先保留证据,确认文件来源和触发路径,再做清理和加固。

技术分析

WebShell 排查通常围绕三个问题展开:

  • 文件是什么时候出现的?
  • 是谁通过什么入口写入的?
  • 是否已经被访问或执行过?

如果只看文件内容,很容易漏掉入口点。例如后门可能由上传漏洞写入,也可能由后台弱口令、插件漏洞、反序列化漏洞、定时任务或供应链文件覆盖引入。日志和时间线会比单点判断更可靠。

关键步骤

1. 保护现场并建立时间线

先对可疑文件和相关日志做只读备份,避免后续操作覆盖访问时间或被日志轮转清理。

mkdir -p /root/incident-$(date +%F)
cp -a /data/www/app /root/incident-$(date +%F)/app-snapshot
cp -a /var/log/nginx /root/incident-$(date +%F)/nginx-logs
stat /data/www/app/path/to/suspect.php
sha256sum /data/www/app/path/to/suspect.php

重点关注文件的 mtime、ctime、属主属组和权限。如果 Web 目录大量文件在同一时间被修改,要考虑批量写入或部署流程异常。

2. 查找近期新增和高风险脚本文件

先按时间过滤,再结合文件类型和路径判断。上传目录、缓存目录、临时目录、静态资源目录里出现可执行脚本都值得重点看。

find /data/www/app -type f -mtime -7 \
  \( -name "*.php" -o -name "*.phtml" -o -name "*.phar" -o -name "*.jsp" -o -name "*.asp" \) \
  -printf "%TY-%Tm-%Td %TH:%TM:%TS %u %g %m %p\n" | sort

如果目录较大,可以先看上传目录和缓存目录:

find /data/www/app/uploads /data/www/app/cache -type f \
  -printf "%TY-%Tm-%Td %TH:%TM:%TS %m %p\n" | sort | tail -100

3. 静态识别可疑代码特征

WebShell 不一定都有明显特征,但以下模式经常出现在后门、混淆代码或异常执行链中:

  • 动态函数调用,如变量函数、拼接函数名
  • 高危执行函数,如 system、exec、shell_exec、passthru、proc_open
  • 编码和压缩组合,如 base64_decode、gzinflate、str_rot13
  • 异常入口变量,如直接处理请求参数并执行
  • 长字符串、高熵内容、不可读混淆块

可以先用 grep 做粗筛:

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

需要注意,命中这些函数不代表一定是后门。有些框架、模板引擎或运维脚本也可能使用动态执行。判断时要结合上下文:函数参数是否来自用户输入,是否存在鉴权,是否有业务合理性。

4. 关联访问日志

找到可疑文件后,先查它是否被访问过,以及访问来源、User-Agent、请求方法、状态码和响应大小。

grep "suspect.php" /var/log/nginx/access.log* | tail -50

如果 access.log 已轮转压缩,可以使用 zgrep:

zgrep "suspect.php" /var/log/nginx/access.log*.gz

接着按文件修改时间前后扩展排查。例如可疑文件 mtime 是 2024-05-10 03:14,可以查看前后 30 分钟内的上传接口、后台接口、插件接口访问情况。

awk '$4 ~ /10\/May\/2024:03:/ {print}' /var/log/nginx/access.log \
  | egrep "upload|admin|plugin|file|editor|api"

这里的重点不是找“看起来最像攻击”的请求,而是还原链路:哪个请求导致文件写入,哪个请求触发了执行。

5. 检查 Web 服务和 PHP-FPM 日志

如果开启了 PHP-FPM slowlog 或 error log,经常能看到异常脚本执行路径、报错堆栈或超时位置。

grep -RIn "suspect.php\|Fatal error\|Warning\|shell_exec\|proc_open" \
  /var/log/php* /var/log/php-fpm* 2>/dev/null

同时检查 Nginx error.log,部分上传绕过、路径解析异常或权限错误会留下痕迹:

grep -RIn "suspect.php\|permission denied\|upstream timed out\|FastCGI" \
  /var/log/nginx/error.log*

6. 确认可疑文件是否具备执行条件

有些文件虽然落地,但不一定能执行。应检查 Web 服务器配置,确认上传目录、缓存目录是否允许 PHP 解析。

nginx -T | grep -nE "location|php|fastcgi|root|alias|uploads"

比较稳妥的配置原则是:上传目录只作为静态文件目录,禁止脚本解析。例如:

location ^~ /uploads/ {
    types { }
    default_type application/octet-stream;
    location ~* \.php$ {
        return 403;
    }
}

如果业务允许上传图片,还应配合服务端 MIME 检查、文件头校验、随机文件名、扩展名白名单和对象存储隔离。

7. 排查持久化和横向影响

WebShell 只是表象,后续要确认是否存在持久化手段:

  • crontab 是否有异常任务
  • systemd 是否有新增服务
  • Web 目录是否存在批量新增文件
  • 用户 SSH authorized_keys 是否被写入异常公钥
  • 数据库是否出现异常管理员账号
  • 业务后台账号是否有异常登录

常用检查命令如下:

crontab -l
ls -al /etc/cron* /var/spool/cron 2>/dev/null
systemctl list-unit-files | grep enabled
find /data/www/app -type f -mtime -7 -printf "%TY-%Tm-%Td %TH:%TM:%TS %p\n" | sort
find /home /root -name authorized_keys -type f -exec ls -al {} \;

如果服务器上存在多个站点,还要检查其他站点目录是否有同类文件。攻击者经常会在同权限可写目录中放多个备份入口。

代码审计关注点

如果日志显示入口来自上传功能,代码审计时建议重点看以下位置:

  • 是否只在前端限制扩展名
  • 服务端是否使用黑名单而不是白名单
  • 是否信任 Content-Type
  • 文件名是否直接使用用户输入
  • 是否允许双扩展名、大小写混淆或特殊字符
  • 上传目录是否位于 Web 可执行路径

一个更稳妥的服务端校验思路是:扩展名白名单、内容类型辅助判断、真实文件头校验、重命名存储、目录不可执行。

// 示例:只表达防御思路,需结合实际框架处理
$allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$ext = strtolower(pathinfo($originalName, PATHINFO_EXTENSION));

if (!in_array($ext, $allowedExt, true)) {
    throw new RuntimeException('invalid extension');
}

$newName = bin2hex(random_bytes(16)) . '.' . $ext;
$target = $uploadDir . '/' . $newName;

if (!move_uploaded_file($_FILES['file']['tmp_name'], $target)) {
    throw new RuntimeException('upload failed');
}

这里不要把“保存成功”当作安全闭环。真正关键的是上传后的文件不能被解释为脚本执行。

处置建议

  • 先隔离主机或限制外部访问,避免继续被触发
  • 备份可疑文件、日志和关键配置,保留时间线
  • 清除确认的后门文件和异常持久化项
  • 修复入口漏洞,例如上传校验、后台弱口令、组件漏洞
  • 轮换相关凭据,包括后台账号、数据库账号、部署密钥
  • 对 Web 目录做权限收敛,运行用户只保留必要写权限
  • 补充监控规则,对新增脚本、异常请求、执行函数告警

权限上建议遵循最小化原则。很多站点被写入后门,是因为整个项目目录都给了 Web 用户写权限。通常只需要 uploads、cache、runtime 等目录可写,代码目录不应由运行用户修改。

chown -R deploy:deploy /data/www/app
chown -R www-data:www-data /data/www/app/uploads /data/www/app/cache /data/www/app/runtime
find /data/www/app -type d -exec chmod 755 {} \;
find /data/www/app -type f -exec chmod 644 {} \;

注意点

  • 不要只依赖特征库。简单混淆、分段拼接、异步加载都可能绕过粗糙规则。
  • 不要忽略正常文件被插入少量恶意代码的情况,尤其是入口文件、配置文件、模板文件。
  • 不要在未备份前直接删除文件,后续很难还原入侵路径。
  • 不要只修复后门文件,不修复入口漏洞等于等待再次写入。
  • 日志时间要注意时区、容器时间和宿主机时间是否一致。

总结

一次有效的 WebShell 排查,不是“grep 到一个 eval”就结束,而是要完成从文件落点、访问日志、入口漏洞到持久化检查的闭环。实际工作里,时间线和权限边界往往比单个样本更重要。

建议平时就做好三件事:上传目录禁止脚本执行,代码目录权限收敛,关键日志集中留存。等到事件发生时,这些基础措施会直接决定排查效率和影响范围。

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

0篇意见

推荐意见

没有意见。

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