背景
这篇整理来自一次站点被植入 WebShell 的应急复盘。目标不是讨论“如何利用”,而是把排查过程、常见痕迹、日志关联方式和后续加固点记录下来,方便以后遇到类似事件时少走弯路。
受影响环境大致是常见的 Java Web 应用,前面有 Nginx 反向代理,后端 Tomcat,业务目录存在文件上传功能。事件表现为:服务器 CPU 偶发升高,站点目录出现异常 JSP 文件,Nginx 访问日志里有少量非常规请求。
建议在授权范围内做排查和复现。本文只覆盖取证、分析和防护,不提供未授权入侵、持久化控制或规避检测的操作。
问题场景
初始告警来自主机侧文件完整性监控:Web 根目录下新增了几个 JSP 文件,文件名看起来像随机字符串,例如 update_2024.jsp、test1.jsp 这类容易混入日常维护文件的命名。
管理员反馈近期没有发布这些文件,也没有临时调试需求。结合访问日志查看,发现这些 JSP 文件被少量外部 IP 请求过,请求时间和文件创建时间接近。
这类事件排查重点一般有几个:
- 确认异常文件的写入时间、属主、权限和内容特征。
- 从 Web 日志还原首次访问、上传入口、可疑参数。
- 确认是否存在命令执行、横向移动、敏感文件读取等后续行为。
- 清理落点后,定位根因并做长期加固,避免只删文件不修洞。
技术分析
先从文件层面看。Linux 下可以用 stat 查看文件时间,结合应用发布记录判断是否异常:
stat /data/www/app/upload/update_2024.jsp
ls -al --time-style=full-iso /data/www/app/upload/
find /data/www/app -type f -name "*.jsp" -mtime -7 -ls需要注意,文件的 mtime、ctime 只能作为线索,不能作为绝对证据。攻击者或者异常程序可能修改时间戳;另一方面,运维备份、解压覆盖也会改变时间。更可靠的方式是同时关联 Web 日志、系统审计日志、应用日志。
在 Nginx 场景中,可以先按异常文件名反查请求:
grep -n "update_2024.jsp" /var/log/nginx/access.log*
grep -n "test1.jsp" /var/log/nginx/access.log*如果日志量比较大,建议按时间窗口筛选。比如文件创建时间在 10:20 左右,可以取前后 30 分钟:
awk '$4 >= "[12/Mar/2024:09:50:00" && $4 <= "[12/Mar/2024:10:50:00" {print}' /var/log/nginx/access.log不同日志格式下,awk 条件需要调整。实战里我更倾向先用 grep 粗筛 URI、状态码、IP,再导入本地做结构化分析。
日志时间线还原
一次比较完整的时间线通常包含三类请求:
- 上传入口请求:可能是
/upload、/file/save、/editor/upload等。 - 落点访问请求:直接访问新写入的 JSP、PHP、ASP 文件。
- 后续探测请求:访问系统信息、配置文件、管理接口、压缩包、备份文件等。
可以先统计异常时间窗口内状态码和 URI:
awk '{print $7, $9}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head -50如果 Nginx 日志记录了请求体长度、User-Agent、Referer,会更好关联。比如上传请求一般 body_bytes_sent 或 request_length 会明显偏大;落点访问可能返回 200 且响应体较小。
常见可疑特征包括:
- 上传接口返回 200 或 302 后,很快访问上传目录下的脚本文件。
- 同一 IP 在短时间内访问多个敏感路径,例如
/WEB-INF/、/actuator/、/.env。 - User-Agent 异常固定,或者为空,和正常浏览器请求差异明显。
- URL 参数包含长字符串、编码后的片段、疑似命令关键字。
如果是 Tomcat,可以继续看 catalina.out、应用日志和访问日志。很多上传失败、类型校验异常、业务报错会留在应用日志里,这些信息对定位入口很有用。
异常文件内容分析
拿到 WebShell 或疑似脚本后,不建议直接在生产环境打开执行。可以先复制到隔离环境,使用文本工具和静态分析方式查看。
file update_2024.jsp
sha256sum update_2024.jsp
strings update_2024.jsp | head -50
sed -n '1,120p' update_2024.jsp分析时关注几个点:
- 是否包含动态执行相关逻辑,例如反射、脚本引擎调用、进程创建、类加载等。
- 是否读取请求参数、请求头、Cookie,并将其传入危险函数。
- 是否对内容做 Base64、URL 编码、字符拼接等混淆。
- 是否落地其他文件,或连接外部地址下载内容。
以 Java Web 为例,风险点通常集中在:
Runtime.getRuntime().exec、ProcessBuilder这类进程调用。- 反射调用
ClassLoader、defineClass等动态加载逻辑。 - 使用
javax.script.ScriptEngine执行脚本。 - 读写 Web 根目录、临时目录、用户家目录下的文件。
这里不建议把样本直接拿去公网沙箱提交,尤其是业务系统路径、内网地址、密钥片段可能会泄露。可以先本地脱敏,或只提取 hash 和关键行为特征。
根因定位:上传链路是重点
很多 WebShell 事件的根因是文件上传校验不完整。常见问题有:
- 只校验前端扩展名,后端未校验。
- 只判断 Content-Type,信任客户端传入的 MIME。
- 允许上传目录被 Web 容器解析脚本。
- 上传后文件名可控,路径拼接缺少规范化处理。
- 富文本编辑器、老旧组件存在已知文件上传漏洞。
代码审计时,可以搜索上传相关入口和危险写文件逻辑:
grep -R "MultipartFile" -n src/main/java
grep -R "transferTo" -n src/main/java
grep -R "FileOutputStream" -n src/main/java
grep -R "getOriginalFilename" -n src/main/java重点看文件扩展名白名单是否严格,是否使用服务端生成文件名,是否限制保存目录,是否对路径做规范化校验。
一个更稳妥的后端处理思路是:
- 服务端生成随机文件名,不使用用户原始文件名作为最终路径。
- 扩展名采用白名单,例如只允许
jpg、png、pdf等业务必需类型。 - 结合文件头魔数做二次校验,但不要只依赖魔数。
- 上传目录不放在 Web 可执行目录下,最好通过对象存储或静态文件服务提供访问。
- 对上传大小、频率、用户权限做限制。
关键处置步骤
现场处置建议按“保全证据、隔离风险、定位根因、清理恢复、加固验证”的顺序做。不要一上来就直接删除文件,否则时间线和样本证据容易丢。
1. 保全样本和日志
mkdir -p /root/incident-20240312/samples
cp -a /data/www/app/upload/update_2024.jsp /root/incident-20240312/samples/
sha256sum /root/incident-20240312/samples/update_2024.jsp > /root/incident-20240312/hash.txt
cp -a /var/log/nginx/access.log* /root/incident-20240312/
cp -a /var/log/nginx/error.log* /root/incident-20240312/如果主机上有审计系统或 EDR,也应导出同一时间窗口内的进程、网络连接、文件变更记录。
2. 临时隔离可疑入口
如果确认上传入口存在风险,可以先临时关闭上传功能,或通过网关限制对应接口访问来源。对于已经落地的脚本文件,建议先阻断执行,而不是直接删除。
Nginx 可临时禁止上传目录解析脚本,示例:
location ^~ /upload/ {
location ~* \.(jsp|jspx|php|asp|aspx)$ {
return 403;
}
}实际配置要结合业务路径调整。Java Web 下更推荐从 Tomcat 或应用部署层面确保上传目录不在可执行路径内。
3. 排查进程和网络连接
WebShell 被访问后,可能触发系统命令、下载工具或反连行为。可以查看异常进程树和网络连接:
ps aux --forest
ss -tunap
lsof -i -P -n
last -a
lastb -a关注 Web 服务用户启动的异常进程,例如 tomcat、www-data、nginx 用户下出现 shell、下载器、压缩工具、扫描工具等。这里只是排查方向,不建议仅凭进程名判断,需要结合启动时间、父进程、命令行和文件路径。
4. 清理落点与持久化项
清理时除了 Web 根目录,还要看临时目录、计划任务、启动项、SSH 授权密钥等常见持久化位置:
find /data/www/app -type f -mtime -14 -ls
find /tmp /var/tmp -type f -mtime -14 -ls
crontab -l
ls -al /etc/cron.*
ls -al ~/.ssh/authorized_keys如果存在多台节点,注意不要只清理一台。负载均衡后面的应用节点、共享存储、备份目录都要检查。
5. 修复代码和配置
根因如果是上传校验问题,需要修代码,而不是靠 WAF 规则长期兜底。WAF 可以作为缓解措施,但上传目录可执行、后端校验缺失这类问题必须从应用和部署层解决。
典型配置加固包括:
- 上传文件存储目录与应用执行目录分离。
- Web 容器禁止上传目录内脚本解析。
- 应用进程最小权限运行,不使用 root 启动。
- 文件写权限按目录收敛,不给整个站点目录写权限。
- 关闭不必要的管理端口和调试接口。
检测规则和持续监控
事件结束后,建议沉淀一些可复用的检测规则。比如对 Web 根目录新增脚本文件做监控,对上传目录出现可执行扩展名做告警。
find /data/www/app/upload -type f \( -name "*.jsp" -o -name "*.jspx" -o -name "*.php" -o -name "*.asp" -o -name "*.aspx" \) -print如果使用 auditd,可以对关键目录写入做审计:
auditctl -w /data/www/app/upload -p wa -k web_upload_write
aureport -f -k | grep web_upload_write生产环境中 auditd 规则要评估性能影响,尤其是高频写目录。更稳妥的方式是结合文件完整性监控、日志平台和主机安全基线统一处理。
代码审计关注点
从长期治理角度,建议把以下点纳入代码审计清单:
| 审计点 | 风险说明 | 建议 |
|---|---|---|
| 文件名处理 | 使用用户原始文件名可能导致覆盖、路径穿越 | 服务端生成文件名,路径规范化 |
| 类型校验 | 仅依赖扩展名或 Content-Type 容易失效 | 白名单、魔数、业务侧多重校验 |
| 存储位置 | 上传目录可执行会放大风险 | 放到非 Web 执行目录或对象存储 |
| 权限控制 | 任意用户可上传敏感类型文件 | 按角色限制上传能力和频率 |
| 异常日志 | 上传失败和拦截缺少记录 | 记录用户、IP、文件名、大小、原因 |
注意点
- 不要只看单个 WebShell 文件。一次入侵里可能有多个落点,甚至不同目录下存在备份木马。
- 不要只依赖文件名判断。很多异常文件会伪装成业务脚本、图片缓存、临时文件。
- 不要在生产环境直接执行样本。静态分析优先,必要时放到隔离环境。
- 不要只删文件不修根因。上传链路、组件版本、权限模型都要检查。
- 不要忽略时间同步。多台服务器时间不一致会影响日志关联。
总结
WebShell 事件的处理核心不是“找到一个文件然后删除”,而是建立完整证据链:异常文件什么时候写入、通过哪个入口写入、被谁访问、执行了什么行为、是否还有其他落点。
从防护角度看,上传目录不可执行、后端严格白名单校验、应用最小权限、关键目录文件监控,是比较基础但有效的措施。应急结束后,把排查命令、时间线、样本 hash、根因代码位置和修复记录沉淀下来,下一次响应效率会高很多。
推荐意见