背景
Webshell 排查是应急响应里很常见的一类工作。很多时候,告警并不会直接告诉你“哪个文件是木马”,而是给出一些零散线索:异常 POST 请求、Web 目录新增文件、进程启动了系统命令、某个 PHP 文件被频繁访问等。
这篇文章整理一套偏实战的排查思路,重点放在日志分析、文件定位、代码审计和加固复盘上。内容只讨论防守侧分析方法,不涉及未授权利用、恶意控制或规避检测。
典型场景
场景可以简化为:一台运行 PHP 站点的服务器出现异常安全告警,告警信息包括:
- Web 服务进程存在异常子进程,例如 sh、bash、cmd、curl、wget 等;
- 站点目录出现近期新增的 PHP 文件;
- 访问日志中存在异常 POST 请求,URL 不属于正常业务接口;
- 部分请求 User-Agent、Referer、请求体长度明显异常;
- 服务器对外发起了不符合业务特征的连接。
实际排查时,不建议一开始就凭经验删除文件。更稳妥的做法是先保全证据,再做定位,最后做清理和修复。
第一步:保全现场与确认影响面
如果是生产环境,建议先做最小化影响处理,例如临时下线可疑入口、限制外联、备份日志和可疑文件。直接重启或清空目录可能导致关键时间线丢失。
# 记录当前时间,方便后续对齐日志时间线
date '+%F %T %z'
# 备份 Web 访问日志与错误日志
mkdir -p /tmp/ir_backup
cp -a /var/log/nginx /tmp/ir_backup/nginx_$(date +%F_%H%M%S)
cp -a /var/log/httpd /tmp/ir_backup/httpd_$(date +%F_%H%M%S) 2>/dev/null
# 备份站点目录元数据与文件清单
find /var/www -printf '%TY-%Tm-%Td %TH:%TM:%TS %u %g %m %s %p\n' > /tmp/ir_backup/web_file_timeline.txt这里的重点是建立时间线。Webshell 往往不是单点事件,可能存在上传入口、落地文件、执行命令、横向探测、清理痕迹等多个阶段。
第二步:从访问日志定位异常入口
先按状态码、请求方法、访问频次、请求大小做粗筛。Webshell 常见访问特征包括异常 POST、少量固定 URL 高频访问、请求参数长度异常、返回体大小稳定等,但这些都不是绝对规则,需要结合业务判断。
# 查看近期 POST 请求,按 URL 聚合
awk '$6 ~ /POST/ {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -50
# 过滤常见可疑扩展访问
awk '{print $1,$4,$5,$6,$7,$9,$10,$12}' /var/log/nginx/access.log | grep -Ei '\.(php|phtml|php5|inc)(\?| )' | head
# 按状态码聚合
awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr
# 查找非常规 User-Agent
awk -F '"' '{print $6}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -30如果使用 Nginx 反代 PHP-FPM,还要关注 upstream 响应时间、请求体大小、客户端 IP 是否来自代理之后的真实地址。若站点前面有 CDN 或负载均衡,需确认日志中记录的是 X-Forwarded-For 还是边缘节点 IP,避免误判。
第三步:检查 Web 目录近期变更
Webshell 通常会落在可写目录,例如上传目录、缓存目录、临时目录、插件目录或主题目录。先按修改时间筛选,再结合文件内容判断。
# 查找 7 天内变更的脚本文件
find /var/www -type f -mtime -7 \( -name '*.php' -o -name '*.phtml' -o -name '*.inc' \) -ls
# 查找上传目录中的脚本文件
find /var/www -type f \( -path '*upload*' -o -path '*uploads*' -o -path '*cache*' \) \
\( -name '*.php' -o -name '*.phtml' -o -name '*.php5' \) -ls
# 查找权限异常的文件
find /var/www -type f -perm -002 -ls
find /var/www -type d -perm -002 -ls需要注意,攻击者可能会修改文件时间戳,单看 mtime 不够。可以结合日志访问时间、文件 inode 变化、备份差异、版本控制记录以及 EDR/HIDS 事件进行交叉验证。
第四步:代码特征审计
Webshell 的代码形态很多,有明文、混淆、编码、拆分加载、伪装业务函数等。审计时不要只依赖单个关键字,但可以用关键函数做第一轮筛选。
# PHP 中常见高风险函数筛选
grep -RInE "(eval|assert|system|exec|shell_exec|passthru|popen|proc_open|base64_decode|gzinflate|str_rot13|create_function)" /var/www --include='*.php'
# 查找长行、疑似混淆代码
find /var/www -name '*.php' -type f -print0 | xargs -0 awk 'length($0) > 500 {print FILENAME ":" FNR ":" substr($0,1,120)}'
# 查找 PHP 文件头部前存在异常二进制或隐藏内容
find /var/www -name '*.php' -type f -exec file {} \; | grep -vi 'PHP\|text'常见可疑点包括:
- 使用动态函数调用,例如变量函数、拼接函数名;
- 对外部输入直接进入命令执行、文件写入、反序列化、模板渲染;
- 大量编码解码叠加,逻辑与业务代码风格明显不一致;
- 文件名伪装成正常资源,例如 image.php、cache.php、class.upload.php;
- 上传目录中出现可执行脚本;
- 业务文件中被插入一小段加载器代码,通过 include 或 require 引入隐藏文件。
判断 Webshell 时要保守,尤其是业务系统、CMS 插件、历史遗留代码中也可能存在高风险函数。建议结合访问日志、文件变更时间、调用链和业务用途综合确认。
第五步:从进程与外联行为反推
如果告警提示 Web 进程启动了系统命令,需要尽快查看进程树、网络连接和历史命令痕迹。生产环境中很多短生命周期进程已经消失,此时可以依赖审计日志、EDR 记录或系统日志。
# 查看 Web 服务相关进程树
ps -ef --forest | grep -E 'nginx|apache|httpd|php-fpm' | grep -v grep
# 查看当前网络连接
ss -plant
# 查看最近登录与异常会话
last -a | head -30
lastb -a 2>/dev/null | head -30
# systemd 日志中检索 Web 服务异常
journalctl --since '24 hours ago' | grep -Ei 'nginx|httpd|php|segfault|error|denied'如果系统开启了 auditd,可以进一步按执行行为检索。没有审计日志时,建议在复盘阶段补上,否则很多命令执行类事件很难还原。
# 示例:查看近期 execve 相关审计事件(前提是已开启审计规则)
ausearch -k web_exec --start recent
# 可配置审计规则示例,监控 Web 用户执行系统命令
-a always,exit -F arch=b64 -S execve -F auid>=0 -F key=web_exec第六步:定位初始入口
只删除 Webshell 不够,关键是找到它是如何落地的。常见入口包括:
- 文件上传校验不严,允许上传 PHP、phtml 或双后缀文件;
- 编辑器、富文本、附件管理组件存在历史漏洞;
- 后台弱口令或凭证泄露,攻击者通过合法功能写入文件;
- CMS、插件、主题存在已知漏洞但未升级;
- 反序列化、模板注入、任意文件写入等代码缺陷;
- 服务器账号权限过大,业务进程可写入不该写的目录。
建议根据可疑文件首次访问时间向前回溯日志。例如某个 shell 文件首次访问时间是 10:32,可以重点查看 10:00 到 10:32 之间的上传接口、后台接口、插件接口请求。
# 粗略按时间窗口筛选访问日志
awk '$4 >= "[10/Jan/2026:10:00:00" && $4 <= "[10/Jan/2026:10:32:59" {print}' /var/log/nginx/access.log > /tmp/ir_backup/window.log
# 在时间窗口中查看上传、编辑、插件相关路径
grep -Ei 'upload|file|attach|editor|plugin|theme|admin|ajax' /tmp/ir_backup/window.log如果站点有应用日志,也要同步检查。访问日志只能看到 HTTP 层信息,应用日志可能记录登录账号、文件名、异常堆栈、业务操作 ID,这些对确定入口非常有价值。
第七步:清理与修复
清理阶段建议按“隔离、比对、替换、验证”的顺序做,不建议只在原文件上手工删除几行可疑代码。
- 将可疑文件复制到隔离目录并计算哈希,便于后续复盘;
- 业务代码从可信版本库或干净备份重新发布;
- 删除上传目录中的脚本执行能力;
- 修复初始漏洞,例如上传校验、鉴权缺陷、组件升级;
- 轮换后台账号、数据库账号、服务器账号、AccessKey 等敏感凭证;
- 检查计划任务、启动项、SSH 公钥、隐藏用户等持久化点;
- 复查是否存在多个 Webshell 或后门加载器。
对于 Nginx + PHP 站点,一个常见加固点是禁止上传目录执行 PHP:
location ^~ /uploads/ {
location ~* \.(php|phtml|php5|inc)$ {
deny all;
}
}
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}同时要控制目录权限。原则上,Web 运行用户只应对必要目录有写权限,代码目录不应由 Web 进程随意写入。
# 示例:代码目录只读,上传目录可写但不可执行需要由 Web 服务配置配合限制
chown -R root:root /var/www/site
chown -R www-data:www-data /var/www/site/uploads
find /var/www/site -type d -exec chmod 755 {} \;
find /var/www/site -type f -exec chmod 644 {} \;
chmod -R 750 /var/www/site/uploads常见误区
- 只按关键字删除文件:容易漏掉加载器、伪装文件和已修改业务文件;
- 只看 Web 目录:需要同时检查进程、计划任务、账号、外联和系统日志;
- 忽略入口修复:Webshell 清掉后很快再次出现,通常是入口还在;
- 没有证据保全:后续无法判断影响面,也难以对外部审计交代;
- 直接在生产环境反复试探:可能破坏时间线,也可能扩大业务影响。
一个简化排查清单
| 阶段 | 检查项 | 目标 |
| 日志分析 | POST、异常 UA、异常路径、状态码 | 定位可疑访问入口 |
| 文件排查 | 近期变更、上传目录脚本、混淆代码 | 发现 Webshell 或加载器 |
| 进程排查 | Web 进程子进程、外联连接 | 确认是否存在命令执行行为 |
| 入口定位 | 上传接口、后台操作、组件漏洞 | 找到初始落点 |
| 清理修复 | 可信发布、权限收敛、凭证轮换 | 降低复发风险 |
| 复盘加固 | 审计日志、WAF/HIDS、监控告警 | 提升后续发现能力 |
总结
Webshell 排查的核心不是“找到一个可疑文件就结束”,而是要把时间线串起来:入口是什么,文件如何落地,执行过什么动作,影响了哪些资产,最后修复了哪些根因。
从经验看,真正有长期价值的做法是把排查过程沉淀成基线:日志字段完整、Web 目录权限清晰、上传目录不可执行、组件有升级机制、关键命令执行有审计。这样下次遇到类似事件时,不会只能靠猜。
推荐意见