背景
最近帮一个中小型业务站点做安全排查,起因是主机侧 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 排查的核心不是“找到一个可疑文件”,而是建立完整证据链:它从哪里来、为什么能写入、为什么能执行、执行后做了什么、修复后如何确认不会复发。
从经验看,上传目录脚本执行、上传接口校验不足、后台权限控制薄弱,是这类事件里最常见的三个问题。处理时建议按“固定现场、日志关联、代码审计、影响排查、修复加固、验证复盘”的顺序推进,这样既能减少遗漏,也方便后续向业务和运维团队说明风险与修复优先级。
推荐意见