跳转到帖子

背景

最近帮一个中小型业务站点做安全排查,起因是主机侧 EDR 报了一条 WebShell 行为告警:Web 进程在站点目录下创建了一个可执行脚本文件,并在短时间内出现了异常 POST 请求。这个场景在实际运维里比较常见,难点不在于“删掉木马”,而是确认入口、影响范围和后续加固是否闭环。

本文整理一次相对完整的排查思路,重点放在日志关联、文件取证、代码审计和修复验证。涉及的命令和样例均为防守排查用途,不包含利用细节。

问题场景

业务环境大致如下:

  • Linux 主机,Nginx + PHP-FPM。
  • 站点存在后台文件上传功能,用于上传图片和附件。
  • 告警文件位于 /data/www/example.com/public/uploads/2024/ 目录。
  • 访问日志中出现针对该文件的多次 POST 请求。

初看像是上传目录被写入了可执行脚本,但实际排查不能只看文件名和路径。常见误区是直接删除可疑文件后结束处理,这样很容易遗漏入口,比如上传校验缺陷、历史插件漏洞、弱口令后台、定时任务被篡改等。

技术分析

1. 先固定现场,避免证据丢失

排查开始时建议先做只读备份,至少包括可疑文件、访问日志、错误日志、应用日志、最近变更文件列表。不要一上来就清空目录或重启服务。

mkdir -p /root/incident-backup/webshell-$(date +%F)
cp -a /data/www/example.com/public/uploads/2024/suspect.php /root/incident-backup/webshell-$(date +%F)/ 2>/dev/null
cp -a /var/log/nginx/access.log* /root/incident-backup/webshell-$(date +%F)/
cp -a /var/log/nginx/error.log* /root/incident-backup/webshell-$(date +%F)/

如果文件仍存在,建议记录哈希、权限、时间戳和所属用户:

sha256sum /data/www/example.com/public/uploads/2024/suspect.php
stat /data/www/example.com/public/uploads/2024/suspect.php
ls -l --time-style=full-iso /data/www/example.com/public/uploads/2024/suspect.php

这里需要注意 Linux 文件时间戳并不一定可靠,攻击者可能修改 mtime,或者部署流程也会改变时间。但它仍然可以作为日志关联的参考点。

2. 从访问日志反推落点

先围绕可疑文件名查访问记录:

grep -R "suspect.php" /var/log/nginx/access.log*

重点关注几个字段:

  • 首次访问时间:可能接近文件写入时间。
  • 请求方法:如果文件首次出现前有上传接口的 POST 请求,需要重点看。
  • 状态码:200、201、302、403、404 的含义不同。
  • User-Agent 和 Referer:不能作为强证据,但可辅助聚类。
  • 源 IP:不要简单归因,更多用于关联请求序列。

然后围绕文件创建时间前后 10 到 30 分钟提取同源 IP 请求轨迹。例如:

grep "12/Mar/2026:10:" /var/log/nginx/access.log | grep "203.0.113.10"

如果日志量比较大,可以用 awk 粗筛上传相关路径:

awk '$6 ~ /POST/ {print $1, $4, $5, $6, $7, $9, $10, $12}' /var/log/nginx/access.log | grep -Ei "upload|file|attach|editor|admin"

这一步的目标不是证明“谁攻击了”,而是还原请求链路:先访问了哪个页面、提交了哪个接口、服务端返回了什么、后续是否访问了上传产物。

3. 检查上传目录是否允许脚本执行

很多 WebShell 落地能执行,并不是因为上传本身一定存在高危漏洞,而是上传目录被 Web 服务当作动态脚本目录处理。对于 Nginx + PHP-FPM,常见风险配置是把所有 .php 请求都转发给 PHP-FPM,且没有限制目录。

可以检查 Nginx 配置中 PHP 解析规则:

nginx -T 2>/dev/null | grep -nE "location|fastcgi|SCRIPT_FILENAME|php"

一个更稳妥的思路是对上传目录禁用脚本解析。例如:

location ^~ /uploads/ {
    location ~* \.(php|phtml|php[0-9]|phar|jsp|asp|aspx)$ {
        return 403;
    }
    try_files $uri =404;
}

如果使用 Apache,也应检查是否存在 .htaccess 覆盖解析规则的情况,尤其是允许用户上传目录内创建配置文件时。

4. 审计上传接口的关键校验点

这类问题通常要回到代码层确认入口。以 PHP 上传功能为例,建议重点看以下几个点:

  • 是否只校验了前端扩展名,服务端没有校验。
  • 是否仅依赖 Content-Type,而没有校验真实文件类型。
  • 文件名是否由用户完全控制,是否存在双扩展名、大小写绕过、特殊字符问题。
  • 上传后保存路径是否位于 Web 可访问目录。
  • 是否对上传文件重新命名,是否保留原始后缀。
  • 是否对图片做了解码重编码,而不是简单移动文件。

防守侧更推荐白名单策略。下面是一个简化示例,展示思路而不是可直接复制到生产的完整代码:

$allowedExt = ['jpg', 'jpeg', 'png', 'gif', 'pdf'];
$originName = $_FILES['file']['name'] ?? '';
$tmpName = $_FILES['file']['tmp_name'] ?? '';

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

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime = $finfo->file($tmpName);
$allowedMime = [
    'jpg'  => ['image/jpeg'],
    'jpeg' => ['image/jpeg'],
    'png'  => ['image/png'],
    'gif'  => ['image/gif'],
    'pdf'  => ['application/pdf'],
];

if (!in_array($mime, $allowedMime[$ext], true)) {
    throw new RuntimeException('invalid mime');
}

$newName = bin2hex(random_bytes(16)) . '.' . $ext;
$target = '/data/upload-storage/' . $newName;
move_uploaded_file($tmpName, $target);

这里有两个关键点:第一,扩展名和 MIME 都应该校验,但不能迷信 MIME;第二,最好把文件保存到非 Web 根目录,再通过受控下载接口读取,避免上传即访问。

5. 判断是否存在二次落地和横向影响

发现一个 WebShell 后,需要继续看是否有后门持久化或二次文件。可以从最近修改文件入手:

find /data/www/example.com -type f -mtime -7 -printf '%TY-%Tm-%Td %TH:%TM:%TS %u %g %m %p\n' | sort

也可以按可疑特征做静态筛查。注意这里不是靠某个关键字定罪,而是用于缩小人工审计范围:

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

同时检查计划任务、启动项、Web 用户权限和临时目录:

crontab -l -u www-data 2>/dev/null
ls -la /var/spool/cron/ 2>/dev/null
ps aux | grep -E "www-data|nginx|php-fpm" | grep -v grep
find /tmp /var/tmp -type f -mtime -3 -ls 2>/dev/null

如果 Web 用户权限过大,比如能写代码目录、能执行系统命令、能访问敏感配置文件,影响面会明显扩大。至少应确认数据库配置、对象存储密钥、第三方接口密钥是否有泄露风险。

关键步骤

阶段目标建议动作
固定现场保留证据备份可疑文件、日志、记录哈希和时间戳
日志关联还原入口围绕文件名、上传接口、源 IP、时间窗口分析请求链
代码审计确认漏洞点检查上传校验、保存路径、文件名处理、鉴权逻辑
影响排查确认是否扩散查最近修改文件、计划任务、异常进程、敏感配置访问
修复加固降低复发概率禁用上传目录脚本执行、改造上传逻辑、收敛权限
验证复盘闭环回归测试、日志监控、补充告警规则

修复建议

结合这次排查,比较有效的修复措施如下:

  • 上传文件存放到非 Web 根目录,通过后端接口受控读取。
  • 上传目录禁止执行脚本,Nginx/Apache 层都要确认。
  • 服务端采用后缀白名单、MIME 校验、必要时图片重编码。
  • 文件名由服务端生成随机值,不信任用户传入文件名。
  • 限制 Web 运行用户权限,代码目录尽量只读,上传目录只允许必要写入。
  • 后台入口增加强认证和访问控制,避免弱口令或越权访问上传接口。
  • 日志保留周期适当拉长,至少覆盖一次完整排障周期。

如果业务允许,建议在网关或 WAF 层增加上传内容检测和异常访问告警,但不要把它当成根本修复。WAF 更适合兜底,代码和部署权限才是根因治理。

注意点

  • 不要只删除 WebShell。删除文件无法证明入口已关闭。
  • 不要只看访问日志。错误日志、应用日志、审计日志经常能补齐链路。
  • 不要轻信文件时间戳。时间戳可以被修改,需要和日志互相印证。
  • 不要把关键字扫描结果直接当结论。很多正常框架也会出现动态调用,需要人工确认上下文。
  • 不要忽略密钥轮换。如果攻击者可能读取配置文件,应及时轮换数据库、缓存、对象存储和第三方 API 凭据。

总结

WebShell 排查的核心不是“找到一个可疑文件”,而是建立完整证据链:它从哪里来、为什么能写入、为什么能执行、执行后做了什么、修复后如何确认不会复发。

从经验看,上传目录脚本执行、上传接口校验不足、后台权限控制薄弱,是这类事件里最常见的三个问题。处理时建议按“固定现场、日志关联、代码审计、影响排查、修复加固、验证复盘”的顺序推进,这样既能减少遗漏,也方便后续向业务和运维团队说明风险与修复优先级。

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

0篇意见

推荐意见

没有意见。

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