问题背景
我们对外的企业门户网站是一套基于 Tomcat 9 + Spring MVC 的老系统,跑在一台放在 DMZ 区的 Linux 服务器上,前面是 Nginx 反向代理,再往外是防火墙做端口映射。这套系统上线好几年,功能改动不多,一直是"能跑就不动"的状态。
某天下午,全流量检测设备(NDR)弹出一条告警:这台 Web 服务器在持续向一个境外 IP 发起可疑加密 HTTP 通信,特征命中"疑似哥斯拉(Godzilla)Webshell 加密流量"。安全同事把工单转给我时,第一反应是"会不会是误报"——毕竟这台机器只是个门户站,没什么敏感数据。但告警的置信度不低,且外联是持续性的,只能按真出事来应急处理。
故障现象
登上服务器先做了一圈粗看,几个现象拼在一起就不像误报了:
- CPU 间歇性飙高:
top里 Tomcat 的 java 进程偶尔冲到 80% 以上,但门户站访问量并不大,业务上没有理由这么忙。 - 异常外联确实存在:
netstat -antp能看到该 java 进程与告警里那个境外 IP 建立着 ESTABLISHED 连接,端口是 443,流量是加密的,抓包也看不出明文。 - 网站被挂了暗链:用搜索引擎
site:检索自己的域名,翻到几个根本不存在的博彩、菠菜类页面路径,点进去会 302 跳转到外部站——典型的被挂马 / SEO 暗链。 - 上传目录里多了陌生文件:门户有个"个人头像上传"功能,落地目录
/data/webapp/portal/upload/avatar/下,除了正常的 jpg/png,还躺着几个.jsp文件,修改时间集中在三天前的凌晨。
到这一步基本可以确认:不是误报,是真被人打进来了,且已经落地了 Webshell、并在拿这台机器做外联跳板和挂暗链。
排查过程
第一步,先确认外联的"主体"是谁。 用 netstat -antp | grep <境外IP> 拿到连接对应的 PID,再 ps -ef | grep <PID>,结果指向的是 Tomcat 自己的 java 进程,而不是某个独立的挖矿木马或反弹 shell 进程。这说明恶意行为是跑在 Web 容器进程里的——要么是落地的脚本 Webshell 被调用,要么是更隐蔽的内存马。
第二步,翻 Web 访问日志找入口。 重点看 Nginx 的 access.log 和 Tomcat 的 localhost_access_log。Webshell 的访问有很强的特征,用几个条件一过滤就浮出来了:
|
|
结果非常清楚:/upload/avatar/x1.jsp 这个路径在过去三天里被同一批源 IP 反复 POST,请求体长度呈规律性变化,User-Agent 是 Java 客户端的默认值——这正是哥斯拉客户端连接 Webshell 的典型行为。
第三步,确认落地文件性质。 打开那几个 .jsp,内容不是明文,而是一段加载器:接收 pass 参数,做 Base64 解码后再 AES 解密,动态编译执行——标准的哥斯拉加密马,磁盘上和流量里都是密文,普通特征匹配很难抓。
第四步,回溯 Webshell 是怎么传上来的。 门户的头像上传接口按理只允许图片。翻代码 + 翻上传接口的请求日志,还原出攻击者的绕过手法:
- 服务端对文件类型的校验用的是黑名单(只拦
.php、.asp),没拦.jsp; - 且校验只看了前端传来的文件后缀字符串,没有做服务端 MIME / 文件头(magic number)校验;
- 最致命的是,上传目录就在 Web 根目录下,且被 Tomcat 当作可执行的 JSP 目录——文件传上去就能直接被当脚本解析执行。
三个问题叠加,就是一条完整的"任意文件上传 → Webshell 落地 → 直接访问执行"的链路。
第五步,排查有没有内存马。 落地文件好清,但更怕对方在容器里注册了反射型的 Filter / Servlet 内存马(磁盘无 class、重启才消失)。对比了 Tomcat 当前已加载的 Filter 链与应用 web.xml 声明的差异,又用 arthas 扫了一遍可疑的动态注册组件,确认这次没有内存马,攻击者只用了落地脚本马。
解决方案
确认范围后按应急响应流程处置,顺序很重要:
- 先隔离、后动手:在防火墙上只放行运维跳板的入站、掐掉那条境外外联,但暂不整机断网、不重启,先保留现场做取证。
- 固定证据:打包
access.log、Webshell 文件、/tmp与 upload 目录、进程与网络快照,计算哈希留存。 - 清除落地马:删除 upload 目录下全部
.jsp文件,全盘find / -name "*.jsp" -newermt "3 days ago"复核有没有藏在别处的第二落点。 - 清内存并重启:处理完落地文件后重启 Tomcat,确保即便有残留动态组件也被清掉。
- 修上传接口(根治):改为扩展名白名单 + 服务端 magic number 校验 + 随机重命名,并把上传目录移出 Web 根、改为独立存储 + 禁止脚本执行。
- 收尾加固:轮转服务器口令与相关密钥、清理被挂的暗链页面、通知搜索引擎重新收录。
根因分析
技术根因是三点缺陷的叠加,缺一不可:文件类型校验用黑名单且不全(漏了 .jsp)→ 只信前端后缀、无服务端内容校验 → 上传目录与 Web 根同源且可执行脚本。任何一环补上,这条攻击链都走不通。而管理上的根因是这套"能跑就不动"的老系统长期无人做安全基线审视,暴露面(对外的可上传接口)一直无监控,直到全流量设备兜底才发现。
预防措施
- 上传接口:扩展名白名单 + 服务端 MIME / 文件头双重校验 + 上传后随机重命名,从源头堵住可控文件名。
- 存储与执行分离:上传目录独立存放,Nginx / Tomcat 层面显式禁止该目录解析 jsp/php(如 location 配置移除脚本 handler),即使传上去也无法执行。
- 最小权限运行:Web 容器用非 root 的低权限用户跑,限制其对系统目录的写权限。
- 纵深检测:部署 RASP / Webshell 检测,定期用河马、D 盾等工具做落地文件基线扫描;保留全流量 NDR 兜底加密马。
- 暴露面治理:对外系统定期做资产梳理与漏洞扫描,把"能跑就不动"的老站纳入常态化安全巡检。
总结
这次事件的隐蔽点在于:外联主体是 Tomcat 自己、木马是加密的、入口是个不起眼的头像上传接口,任何一个单看都容易被当成"业务正常"或"误报"放过。真正把线索串起来的是全流量告警 + Web 访问日志的高频 POST 特征分析这两把钥匙。对运维来说,最该记住的不是"哥斯拉长什么样",而是:任意文件上传永远要按白名单 + 内容校验 + 存储执行分离三件套来防,而不是靠拦几个危险后缀;同时对外暴露面必须有日志、有监控、有兜底,别让老系统成为攻击者最舒服的落脚点。