背景
最近整理了一起典型的 WebShell 入侵事件。场景并不复杂:一台对公网开放的业务服务器,运行常见的 PHP Web 应用,前面挂了反向代理。管理员最早发现的问题是站点偶尔返回异常内容,随后在 Web 目录下发现了可疑 PHP 文件。
这类事件在实际处置中很常见,难点往往不在于“删掉木马文件”,而在于确认入口、影响范围、是否存在持久化、以及如何把同类问题彻底堵住。本文按一次相对完整的排查流程整理,重点放在日志分析、文件取证、代码入口定位和加固闭环上。
问题场景
已知现象包括:
- Web 根目录下出现异常 PHP 文件,文件名看起来像缓存或资源文件。
- Nginx 访问日志中存在少量非常规请求,User-Agent 较少见。
- 服务器负载没有明显升高,未发现大规模扫描或挖矿行为。
- 应用目录中部分文件修改时间集中在某个时间段。
这种情况下,不建议直接重装或清空日志。比较稳妥的做法是先保护现场,复制关键日志和可疑样本,再进入清理与加固阶段。
第一步:保护现场与基础信息采集
如果业务允许,优先将站点切到维护页或只读状态。至少应避免攻击者继续写入文件。基础信息采集建议覆盖以下几类:
- Web 访问日志、错误日志、反向代理日志。
- 应用日志,例如框架日志、上传日志、后台操作日志。
- 系统登录日志、计划任务、进程列表、网络连接。
- Web 目录文件列表、修改时间、权限信息。
示例命令如下,注意将输出保存到独立目录,避免覆盖现场文件:
mkdir -p /root/incident-$(date +%F)
cd /root/incident-$(date +%F)
cp /var/log/nginx/access.log* ./ 2>/dev/null
cp /var/log/nginx/error.log* ./ 2>/dev/null
cp /var/log/secure* ./ 2>/dev/null
cp /var/log/auth.log* ./ 2>/dev/null
ps auxf > ps_auxf.txt
ss -antup > ss_antup.txt
crontab -l > crontab_current_user.txt 2>/dev/null
ls -lah /etc/cron* > cron_files.txt 2>/dev/null
find /var/www -printf '%TY-%Tm-%Td %TH:%TM %u %g %m %p\n' > web_files_timeline.txt这里的目标不是一次性找出所有答案,而是先建立可回溯的数据基础。很多事件后期无法判断入口,根因就是早期清理过快,日志和样本被破坏。
第二步:定位异常文件
WebShell 通常会伪装成缓存、图片、配置备份或随机命名文件。排查时可以从时间线、文件内容、权限异常三个角度入手。
先看近期修改的 PHP 文件:
find /var/www -type f -name '*.php' -mtime -14 -printf '%TY-%Tm-%Td %TH:%TM %s %p\n' | sort再粗略检索高风险函数和可疑编码特征。这里不是说出现这些函数就一定是木马,而是用于缩小审计范围:
grep -RInE "(eval\s*\(|assert\s*\(|base64_decode\s*\(|gzinflate\s*\(|shell_exec\s*\(|system\s*\(|passthru\s*\(|proc_open\s*\()" /var/www --include='*.php'还可以关注图片目录、缓存目录中是否存在 PHP 文件:
find /var/www -type f \( -path '*upload*' -o -path '*uploads*' -o -path '*cache*' -o -path '*tmp*' \) -name '*.php' -print在这次事件中,异常文件位于上传目录下,文件名前缀类似正常图片资源,但扩展名为 PHP。文件内容经过简单混淆,包含动态函数调用和外部输入拼接。这里不展开具体样本细节,重点是确认它不是业务代码的一部分,并记录其哈希、路径、时间戳和权限。
sha256sum /var/www/html/uploads/疑似文件.php
stat /var/www/html/uploads/疑似文件.php第三步:从访问日志回溯入口
发现 WebShell 后,不要只看这个文件本身,还要追溯它第一次被访问、第一次被写入前后的请求。可以按文件名搜索访问日志:
zgrep -n "疑似文件.php" /var/log/nginx/access.log*如果文件名没有直接出现在日志中,可能是通过上传接口写入,再由攻击者访问。此时应围绕文件修改时间向前后扩展窗口,例如前后 30 分钟:
# 假设可疑文件修改时间为 2025-01-10 14:32 左右
grep "10/Jan/2025:14:" /var/log/nginx/access.log > window_14.log
grep "10/Jan/2025:15:" /var/log/nginx/access.log > window_15.log重点关注:
- 上传接口、附件接口、头像接口、富文本编辑器接口。
- 返回码为 200、302、500 的非常规 POST 请求。
- 请求体较大的访问记录,尤其是上传类路径。
- 同一 IP 在短时间内先探测接口,再访问新文件的行为。
- User-Agent、Referer 缺失或高度固定的自动化请求。
如果 Nginx 未记录请求体,可以结合应用日志判断上传参数、文件名、用户 ID 等信息。很多情况下,入口并不是单纯的“弱口令后台”,而是上传校验缺陷、历史组件漏洞或编辑器插件遗留接口。
第四步:判断是否存在二次落地与持久化
WebShell 只是入口或操作通道之一。事件处置中需要确认攻击者是否写入了其他文件、添加了计划任务、修改了启动项或创建了系统账号。
建议检查近期新增和修改文件:
find /var/www -type f -newermt '2025-01-10 14:00:00' ! -newermt '2025-01-10 16:00:00' -printf '%TY-%Tm-%Td %TH:%TM %s %p\n' | sort检查系统账号和登录痕迹:
last -a | head -50
lastlog | head -50
awk -F: '$3 >= 1000 {print}' /etc/passwd检查计划任务和启动项:
crontab -l 2>/dev/null
ls -lah /var/spool/cron/ 2>/dev/null
ls -lah /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/ 2>/dev/null
systemctl list-unit-files --type=service | grep enabled检查异常网络连接和进程:
ps auxf
ss -antup这里要注意一个误区:没有异常进程,并不代表没有入侵;没有系统登录,也不代表没有 Web 层操作。WebShell 场景下,很多动作都发生在 Web 服务用户权限下,需要结合 Web 日志、文件时间线和应用日志一起判断。
第五步:分析代码层入口
如果日志显示异常请求集中在上传接口,应重点审计上传链路。常见问题包括:
- 只校验前端扩展名,后端未做强校验。
- 仅检查 Content-Type,未检查实际文件内容。
- 允许用户控制保存路径或文件扩展名。
- 上传目录允许 PHP、JSP、ASP 等脚本执行。
- 历史编辑器、文件管理插件未删除或未鉴权。
一个相对稳妥的上传处理思路应包含:
- 服务端白名单限制扩展名。
- 生成随机文件名,不信任原始文件名。
- 校验文件头和实际内容类型。
- 上传目录禁止脚本执行。
- 文件访问与上传目录分离,必要时走对象存储或独立静态域名。
例如 Nginx 层可以对上传目录禁用 PHP 解析,配置示例:
location ~* ^/uploads/.*\.php$ {
return 403;
}
location /uploads/ {
types {
image/jpeg jpg jpeg;
image/png png;
image/gif gif;
application/pdf pdf;
}
default_type application/octet-stream;
}如果使用 PHP-FPM,还要避免过宽的 PHP 解析规则。历史上不少问题来自类似“任意路径匹配到 PHP 解释器”的配置。建议只允许明确的 .php 文件进入 PHP-FPM,且不要对上传目录放行脚本解析。
日志分析中的几个实用技巧
为了快速筛选异常请求,可以先统计状态码、路径和来源 IP:
awk '{print $9}' access.log | sort | uniq -c | sort -nr | head
awk '{print $7}' access.log | sort | uniq -c | sort -nr | head
awk '{print $1}' access.log | sort | uniq -c | sort -nr | head筛选 POST 请求:
grep '"POST ' access.log | awk '{print $1, $4, $6, $7, $9, $10}' | head -100查找访问上传目录中的脚本文件:
grep -E 'uploads/.*\.(php|phtml|phar)' access.log查看异常 UA:
awk -F'"' '{print $6}' access.log | sort | uniq -c | sort -nr | head -30如果访问日志字段经过定制,上述 awk 需要按实际格式调整。建议平时将 request_time、upstream_response_time、host、request_body_size、http_x_forwarded_for 等字段加入日志格式,事件复盘时会非常有帮助。
清理与恢复建议
确认范围后再进入清理阶段。基本顺序建议如下:
- 隔离或下线受影响实例,保留磁盘快照和关键日志。
- 删除确认的 WebShell 和异常落地文件。
- 回滚被篡改的业务代码,优先使用可信发布包或版本库。
- 修复入口漏洞,例如上传校验、组件漏洞、弱口令后台。
- 重置后台账号、服务器账号、数据库账号、密钥和 Token。
- 检查 Web 服务用户权限,避免拥有过大的写权限。
- 上线后加强监控,关注同类路径是否再次出现请求。
如果无法确认攻击者是否获取了数据库配置或环境变量,建议按“已泄露”处理,及时轮换凭据。很多二次入侵不是通过原漏洞,而是通过第一次事件中泄露的账号、密钥或后台 Cookie。
加固重点
| 风险点 | 建议措施 |
|---|---|
| 上传目录可执行脚本 | Web 服务器层禁止脚本解析,上传文件与业务代码分离 |
| 后台弱口令 | 启用强密码、MFA、IP 限制和登录审计 |
| 历史组件未更新 | 建立组件清单,定期核查 CVE 与官方补丁 |
| 日志字段不足 | 记录请求时间、状态码、来源 IP、UA、请求大小等关键字段 |
| Web 用户权限过大 | 最小权限运行,业务代码目录尽量只读 |
| 缺少文件监控 | 对 Web 目录新增脚本文件、关键配置变更做告警 |
几个容易忽略的注意点
- 不要只删除一个 WebShell 文件。应围绕时间线检查同一时间段所有新增和修改文件。
- 不要完全依赖杀毒或查杀脚本。混淆变形后的样本可能无法被规则命中。
- 不要忽视反向代理后的真实 IP。需要确认 X-Forwarded-For 是否可信,避免误判来源。
- 不要在未修复入口前恢复业务。否则攻击者可能很快再次写入。
- 不要把备份压缩包、数据库导出文件放在 Web 可访问目录下。
总结
WebShell 事件处置的核心不是“看到木马就删”,而是建立完整链路:发现异常文件、回溯访问日志、确认写入入口、排查持久化、清理恢复、修复漏洞、持续监控。
从长期治理角度看,上传目录禁执行、组件及时更新、后台访问收敛、日志字段完善、凭据定期轮换,这几项投入不大,但能显著降低类似事件的处置成本。真正有价值的复盘,是把一次入侵变成一组可验证的安全改进项,而不是停留在单次清理。
推荐意见