ARTICLE · 1038160
一次模板注入RCE的溯源分析
一次模板注入RCE的溯源分析





1. 现象
起因是一台ECS被攻击,已经被当成跳板横向,并留下了扫描工具和history。

2.溯源
本地起了java后端程序,分析jar日志。
2026-09-15 00:10:36 [http-nio-8080-exec-17] WARN AbstractRememberMeManager:449 - There was a failure while trying to retrieve remembered principals...Caused by: javax.crypto.AEADBadTagException: Tag mismatch!58 次密集的 rememberMe 解密,全部都是 AEADBadTagException: Tag mismatch ,什么年代了,大概率没有默认key了,印象中jeecgboot没有shiro可以利用的版本,另外大量无效的SQL注入基本也可以忽略。

jeecgboot存在freemarker注入,都是jmreport,验证当前应用中jmreport并没有对外开放。
但是报错都在SysMessageTemplate(我原来也不知道信息接口调用了freemarker模板)
freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:==> ObjectConstructor [in template "SysMessageTemplate" at line 1, column 7]- Failed at: ${ObjectConstructor?new()("javax.scri...分析modules/message/controller/SysMessageTemplateController.class
RequestMapping(value=["/sys/message/sysMessageTemplate"])public ... sendMessage(MsgParams); PostMapping(value=["/sendMsg"])找到对应请求日志。

@PostMapping({"/sendMsg"})public Result<SysMessageTemplate> sendMessage(@RequestBody MsgParams msgParams) { Result<SysMessageTemplate> result = newResult(); Map<String, String> map = null;try { map = (Map<String, String>)JSON.parse(msgParams.getTestData()); } catch (Exception e) { result.error500("); return result; } boolean is_sendSuccess = this.pushMsgUtil.sendMessage(msgParams.getMsgType(), msgParams.getTemplateCode(), map, msgParams.getReceiver()); if (is_sendSuccess) { result.success("); } else { result.error500("); } return result; }}这里pushMsgUtil.sendMessage最终实现了模板注入,所以完整利用链。
POST /hmfy-backend/sys/message/sysMessageTemplate/sendMsg │ ├─ SysMessageTemplateController.sendMessage(MsgParams msgParams) │ map = (Map<String,String>) JSON.parse(msgParams.getTestData()) │ ├─→ this.pushMsgUtil.sendMessage(msgType, templateCode, map, receiver) │ 实现类:PushMsgUtil.sendMessage() [PushMsgUtil.java:33] │ · sysMessageTemplateService.selectByCode(templateCode) │ —— 按 code 查出 /add 存的那条模板记录 │ · content = sysSmsTemplate.getTemplateContent() │ —— 取出攻击者写入的原始恶意内容 │ ├─→ newTemplate("SysMessageTemplate", content, this.freemarkerConfig) │ · freemarkerConfig 是 @Autowired 注入的 Spring Boot 默认自动装配 Bean │ · 【没有设置 TemplateClassResolver,没有 SAFER_RESOLVER,没有任何加固】 │ ├─→ template.process(map, stringWriter) │ · ${"freemarker.template.utility.Execute"?new()("ll")} —— 执行 │ ├─← content2 = stringWriter.toString() —— 渲染后的字符串,包含命令执行结果 │ ├─← sysMessage.setEsContent(content2) │ sysMessageService.save(sysMessage) —— 存进 sys_message 表 │ └─ 返回 true/false 给 Controller,Controller 返回"发送成功/失败"3.内存马

日志中还有大量过滤器的报错,攻击者植入了Filter型内存马。


全局挂载,靠"POST + 是否带 Sec-Ch-Ua 头"区分隧道流量与正常业务流量。