夜雨聆风学习资料网

ARTICLE · 1038162

一次模板注入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 头"区分隧道流量与正常业务流量。

相关学习资料