2026版FreeMarker模板注入攻防指南:从漏洞原理到零信任防护

发布时间:2026/7/1 20:47:46
2026版FreeMarker模板注入攻防指南:从漏洞原理到零信任防护 1. 项目概述为什么2026年还需要关注FreeMarker模板注入如果你是一名应用安全工程师、渗透测试人员或者正在负责企业Java Web应用的安全建设那么“模板注入”这个词对你来说一定不陌生。但提到FreeMarker可能很多人会觉得它有点“老派”远不如Spring Boot自带的Thymeleaf或者Velocity那么“时髦”。然而现实情况是大量遗留系统、金融、电信等行业的传统核心业务依然重度依赖FreeMarker作为其视图层模板引擎。我最近在几个大型企业的红蓝对抗和代码审计项目中依然能频繁地发现由FreeMarker模板注入SSTI导致的高危漏洞。这促使我系统性地梳理了最新的攻击手法、审计思路和防御策略。“2026版”这个前缀并非噱头。它意味着我们需要超越过去那些基于new Template()、getTemplate等明显危险函数的简单扫描去应对更隐蔽的利用链、框架特性滥用以及云原生和零信任架构下的新挑战。本指南旨在提供一套从漏洞挖掘、原理深入理解到最终融入零信任防护体系的完整实战方案。无论你是想入门SRC漏洞挖掘的新手还是希望提升企业纵深防御能力的安全负责人都能从中找到可直接复现的路径和深入思考的视角。2. 核心漏洞原理与攻击面演进要有效挖掘和防御必须深刻理解漏洞的根源。FreeMarker模板注入的本质是攻击者能够控制模板内容或数据模型从而执行任意Java代码。2.1 FreeMarker模板引擎的工作机制FreeMarker的核心是将模板文件.ftl与数据模型Data Model结合渲染输出最终文本如HTML、XML。数据模型通常是一个Map或JavaBean包含了所有模板中可访问的变量。模板中通过${expression}或#directive来引用和操作这些变量。漏洞产生的关键点在于FreeMarker的表达式语言非常强大。它不仅能进行简单的属性取值${user.name}还能进行方法调用${object.method()}、内建函数处理${value?api.someMethod()}以及访问静态类${com.example.SomeClassMETHOD}。当攻击者能够控制expression的内容时危险便产生了。2.2 漏洞入口点从显式到隐式早期的FreeMarker SSTI案例多集中于开发者显式调用API构造模板的场景直接构造模板new Template(name, new StringReader(templateContent), cfg)其中templateContent用户可控。加载模板文件cfg.getTemplate(templateName)其中templateName用户可控且服务器存在任意文件读取可指向恶意.ftl文件。然而在现代应用中如此直白的漏洞已较少见。现在的攻击面更加隐蔽数据模型污染用户输入未经充分过滤直接放入数据模型如model.put(input, request.getParameter(input))而模板中恰巧有${input}或更危险的${input?api}。表达式拼接开发者为实现动态功能将用户输入拼接到模板表达式字符串中例如String expr user. userControlledProperty;然后在模板中通过#assign value expr?eval进行求值。自定义指令/函数暴露应用自定义了FreeMarker指令或函数但其实现内部使用了不安全的反射或?eval且参数用户可控。配置不当FreeMarker的Configuration对象配置了过高的权限。最经典也最危险的是cfg.setNewBuiltinClassResolver(TemplateClassResolver.ALL_CLASSES_TEMPLATE_RESOLVER)或自定义解析器允许访问危险类。2.3 2023-2025年观察到的攻击手法演进基于近期的实战和公开研究攻击手法主要在绕WAF和挖掘深层次利用链上发展?api内建函数的滥用这是目前最高效的利用方式。?api将变量暴露为其底层的Java API。例如如果${value}是一个String那么${value?api}就变成了一个java.lang.String对象可以调用其所有方法。攻击者可以从一个简单的字符串开始通过链式调用最终达到执行命令的目的例如${a?api.class.forName(java.lang.Runtime).getMethod(getRuntime).invoke(null).exec(calc)}。这种方式常能绕过基于“Class”、“Runtime”等关键词的简单过滤。利用Spring上下文在Spring MVC集成FreeMarker的应用中模板自动拥有Spring应用上下文的访问能力。攻击者可能尝试通过${applicationContext}或${springMacroRequestContext}等隐含对象来访问Bean进而执行代码。无数字字母的混淆技术通过FreeMarker的内建函数如?c字符串转字符、?number、集合操作等构造出所需的字符再拼接成恶意类名和方法名以绕过内容安全策略。针对ClassResolver的绕过即使配置了相对安全的ClassResolver也可能因白名单过宽或解析逻辑缺陷被绕过。攻击者会系统性地探测可用的类。注意在审计时不要只盯着“执行命令”。模板注入可能导致敏感信息泄露通过访问${configuration}获取数据库配置、服务器文件读取、甚至作为跳板进行内网横向移动。3. 漏洞挖掘实战从黑盒到白盒漏洞挖掘是一个系统性的过程需要结合黑盒测试的直觉和白盒审计的精准。3.1 黑盒模糊测试与手工探测当面对一个未知应用时我们可以通过以下步骤进行探测识别FreeMarker使用痕迹观察HTTP响应头寻找Server、X-Powered-By等字段是否包含FreeMarker相关信息。触发错误页面看是否包含FreeMarker、TemplateProcessingException等关键字。检查静态资源引用或页面注释中是否有.ftl文件路径。注入点探测参数测试对所有用户输入点GET/POST参数、Cookie、Header尝试注入FreeMarker表达式。最简单的探测payload如${7*7}观察返回是否为49。更隐蔽的可以用${“freemarker”?api.class}看是否返回class freemarker.template.utility.Execute或触发错误。上下文识别如果${7*7}被原样输出尝试#{7*7}旧版本语法或#assign x7*7${x}。有时应用会过滤${但遗漏其他语法。错误信息利用故意注入语法错误的payload如${观察返回的错误信息这能极大帮助确认漏洞存在和了解环境。利用链构造一旦确认注入点开始构造利用链。一个通用的测试思路是第一步获取类对象。${“a”?api.class}或${object.class}。第二步遍历类加载器或寻找Runtime。${java.lang.ClassforName(“java.lang.Runtime”)}。第三步反射调用方法。利用?api.method()或内建函数?new、?constructor等。使用现成的工具辅助如tplmap需适配FreeMarker语法或Burp Suite的FreeMarker SSTI Scanner插件但绝不能完全依赖工具手工理解至关重要。3.2 白盒代码审计关键代码模式白盒审计能更精准地定位问题。你需要重点关注以下几类代码模式模板引擎初始化代码查找Configuration的实例化和配置位置。重点检查setTemplateLoader、setSharedVariable、以及最重要的setNewBuiltinClassResolver、setAPIBuiltinEnabled。// 危险配置示例 Configuration cfg new Configuration(Configuration.VERSION_2_3_31); cfg.setNewBuiltinClassResolver(TemplateClassResolver.ALL_CLASSES_TEMPLATE_RESOLVER); // 允许所有类访问 cfg.setAPIBuiltinEnabled(true); // 启用?api内建函数风险增高模板渲染调用点template.process(dataModel, writer)Template类的getTemplate、createTemplateSpring的FreeMarkerViewResolver相关配置检查视图名是否用户可控。用户输入流入数据模型的路径搜索model.addAttribute()、model.put()、ModelAndView.addObject()等调用追溯其参数来源是否直接来自HttpServletRequest。检查自定义的TemplateDirectiveModel或TemplateMethodModelEx实现其execute或exec方法的参数是否用户可控且未过滤。表达式动态求值全局搜索?eval、?interpret、Environment的eval/interpret方法。这是最高危的模式之一。// 高危代码示例 String userExpr request.getParameter(expr); TemplateModel result env.eval(userExpr); // 直接求值用户输入3.3 审计工具链与辅助脚本纯人工审计效率低需要借助工具静态分析工具SAST使用Semgrep、CodeQL编写自定义规则来捕捉上述危险模式。例如编写规则查找cfg.setNewBuiltinClassResolver调用且参数为不安全常量的情况。IDE全局搜索利用IntelliJ IDEA或Eclipse强大的搜索功能对关键词如“Template”、“?eval”、“ClassResolver”进行跨项目搜索。自定义Gadget探测脚本编写一个简单的Java程序模拟目标应用的FreeMarker配置然后自动尝试加载一系列已知的危险类如freemarker.template.utility.Execute、java.lang.ProcessBuilder测试其是否在ClassResolver的白名单/黑名单之外。实操心得在白盒审计中我习惯先快速通读web.xml或Spring Boot的启动类找到FreeMarker配置类的位置。这就像拿到了地图的“图例”能让你快速理解整个应用的模板安全基线。然后再以“数据流”的方式从Controller层接收参数的地方开始跟踪数据如何被放入Model最终在哪个.ftl文件中被以何种方式引用。这条路径往往就是漏洞链。4. 漏洞利用链深度解析与构造理解漏洞原理后我们需要掌握如何构造有效的利用链。这里分几个层次讲解。4.1 基础利用链命令执行与文件读取假设我们已经有一个可执行任意表达式的注入点并且ClassResolver配置宽松。通过freemarker.template.utility.Execute执行命令 这是FreeMarker内置的一个危险类。如果它未被禁止利用起来最简单。#assign exfreemarker.template.utility.Execute?new() ${ ex(open -a Calculator) }或者使用?api链${freemarker.template.utility.Execute?api.newInstance()(calc.exe)}通过Java反射调用Runtime 这是更通用的方法不依赖FreeMarker特定类。#assign rtjava.lang.RuntimegetRuntime() ${ rt.exec(whoami) }或者${java.lang.ClassforName(java.lang.Runtime).getMethod(getRuntime).invoke(null).exec(id)}文件读取 利用java.io.FileInputStream和java.util.Scanner或org.apache.commons.io.IOUtils。#assign isjava.io.FileInputStreamnew(/etc/passwd) #assign scjava.util.Scannernew(is).useDelimiter(\\A) ${ sc.hasNext() ? sc.next() : }4.2 绕过受限环境当?api和危险类被禁用在实际安全配置中管理员通常会禁用?api和黑名单类。这时需要更巧妙的绕过。利用ObjectWrapper和BeansWrapper FreeMarker在包装对象时可能会暴露一些getter方法。如果数据模型中有一个ServletRequest对象Spring中可能是request你可以尝试${request.servletContext}来获取ServletContext进而可能找到其他有用的Bean或属性。利用Spring的隐含变量 在Spring MVC环境中即使FreeMarker配置严格Spring也可能自动向模型添加一些对象。尝试访问以下变量${springMacroRequestContext}可能暴露应用上下文信息。${request}、${session}、${application}对应ServletContext。通过${request.getServletContext().getAttributeNames()}来枚举属性寻找有用的类实例。字符构造与混淆 如果表达式被执行但返回结果被过滤或转义可以通过构造字符来拼接命令。#assign aa?api.class.forName(java.lang.Runtime)或者利用数字转字符${(65)?char}得到A。4.3 高级利用从SSTI到内存马与横向移动在攻防演练中模板注入的价值远不止于弹个计算器。写入Webshell 如果能执行命令且有写权限可以直接写文件。更优雅的方式是利用FreeMarker的TemplateLoader。如果存在FileTemplateLoader且路径可控可以尝试将恶意模板内容写入一个.ftl文件然后通过访问该模板的URL来触发执行。注入内存马 通过执行Java代码可以利用Java Agent技术或直接使用工具类如java.lang.instrument.Instrumentation动态修改已加载类的字节码注入Filter型或Controller型内存马。这需要攻击者对Java内存马技术有深入理解并且目标环境具备相应条件如开启-javaagent参数或存在可利用的组件。作为内网探测跳板 利用Runtime执行命令可以调用curl、nslookup、ping等命令对内网进行探测。或者利用Java网络库java.net.HttpURLConnection直接发起HTTP请求扫描内网服务。注意事项在实战利用时务必考虑命令回显的问题。如果页面不回显执行结果你需要考虑使用curl或wget将结果外带DNSLog、HTTP请求或者写入一个临时文件再从Web路径访问。同时注意命令的跨平台兼容性Linux vs Windows。5. 零信任架构下的纵深防护方案“零信任”的核心思想是“从不信任始终验证”。将这一理念应用于FreeMarker模板注入的防护意味着我们不能只依赖边界WAF或简单的黑名单而需要在应用生命周期的各个层面建立防御。5.1 安全开发层代码与配置加固这是最根本的一层需要在开发阶段就植入安全基因。严格的FreeMarker配置禁用?api除非业务绝对需要否则在Configuration中设置cfg.setAPIBuiltinEnabled(false)。这是关闭高风险功能的最有效方式。使用安全的ClassResolver绝对不要使用ALL_CLASSES_TEMPLATE_RESOLVER。推荐使用TemplateClassResolver.SAFER_RESOLVERFreeMarker 2.3.17或者自定义一个严格的白名单解析器。cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER); // 或者自定义 cfg.setNewBuiltinClassResolver((className, env, template) - { if (className.equals(java.lang.String) || className.startsWith(com.yourcompany.safe.)) { return ClassUtil.forName(className); } else { throw new TemplateException(Access to class className is forbidden., env); } });设置模板加载路径使用FileTemplateLoader或ClassTemplateLoader时将其根目录限制在安全的、仅包含模板文件的目录下防止目录穿越。安全的编码实践输入净化与输出编码所有用户输入在放入数据模型前必须根据其在模板中的使用场景进行净化。如果作为文本显示进行HTML编码如果作为模板的一部分必须严格验证其内容禁止包含FreeMarker指令标签#,${,[#,[等。避免动态模板/表达式求值彻底杜绝使用?eval、?interpret或Environment.eval()。任何需要动态逻辑的地方都应该在Java代码中实现然后将结果传递给模板。最小权限数据模型不要将整个HttpServletRequest或ServletContext对象放入数据模型。只传递模板渲染所必需的最小数据集。5.2 运行时防护层RASP与动态检测在应用运行时提供额外的保护。应用运行时自我保护RASP部署RASP Agent它可以注入到FreeMarker的核心类如Template、Environment中在模板解析和渲染的关键节点进行钩子Hook。检测逻辑监控TemplateClassResolver的解析行为对尝试加载的类名进行实时分析阻断对java.lang.ProcessBuilder、freemarker.template.utility.Execute等危险类的加载。监控?api的调用栈识别异常的反射调用链。RASP的优势在于能结合应用上下文进行精准判断误报率低且能防御未知攻击手法。Web应用防火墙WAF增强传统的基于正则表达式的WAF规则如检测Runtime、exec很容易被混淆绕过。现代WAF应结合语义分析能够解析FreeMarker表达式的基本结构识别出嵌套的方法调用、反射等危险模式。可以与RASP联动WAF作为第一道粗筛将可疑请求标记并传递给RASP进行深度分析。5.3 资产与流程管控层零信任的落地零信任要求对访问主体、资源、请求进行持续验证。微服务间模板调用的身份认证如果存在A服务调用B服务的模板渲染接口必须使用强身份认证如mTLS、JWT确保只有合法的服务才能发起请求防止内部接口被滥用。动态权限与访问策略对于管理后台等可以上传或修改模板的功能实施基于角色的访问控制RBAC和属性基访问控制ABAC。记录所有模板修改操作并定期进行安全审计。持续安全监控与审计集中收集所有应用日志特别是FreeMarker抛出的TemplateException。异常的模板解析错误可能是攻击尝试的信号。建立针对模板渲染接口的流量基线监控异常访问模式如大量携带特殊参数的请求、来自异常IP的访问。定期进行代码审计和渗透测试将FreeMarker SSTI作为必查项。5.4 供应链安全第三方依赖管理FreeMarker本身作为依赖其安全性也至关重要。持续更新及时将FreeMarker升级到最新稳定版以获取安全修复。关注其安全公告。SCA工具扫描使用软件成分分析SCA工具持续监控项目中所有依赖包括FreeMarker及其传递依赖是否存在已知漏洞CVE。自定义模板指令/函数的安全审查对业务中自行开发的TemplateDirectiveModel或自定义函数进行严格的安全代码审查确保其内部逻辑安全。6. 企业级审计流程与实战案例复盘将上述所有点串联起来形成一套可重复的企业内部审计流程。6.1 四阶段审计流程信息收集阶段识别应用技术栈通过构建脚本、组件清单、接口文档确认是否使用FreeMarker。梳理模板使用场景列出所有涉及模板渲染的URL接口、后台功能点如邮件模板、报告生成、页面渲染。获取代码权限申请对应代码仓库的只读权限。静态代码分析阶段使用SAST工具进行全量扫描生成初步报告。人工复核SAST报告并重点审计Configuration配置类、所有Controller中向Model添加数据的逻辑、以及全局搜索?eval等危险函数。绘制关键数据流图标记从用户输入到模板渲染的完整路径。动态验证与利用阶段根据白盒分析结果在测试环境构造对应的HTTP请求进行验证。使用手工payload和工具进行模糊测试尝试触发异常或执行成功。验证漏洞危害程度尝试构造文件读取、命令执行等利用链。报告与修复建议阶段编写详细漏洞报告包含漏洞位置、触发条件、利用方式、风险等级、修复建议提供安全的代码样例。推动开发团队修复并建议将安全的FreeMarker配置和编码规范纳入开发手册。6.2 实战案例复盘某OA系统模板注入漏洞背景在对某企业旧版OA系统进行授权测试时发现其公文预览功能存在异常。过程黑盒探测发现预览接口接收一个templateId参数和一个content参数。修改content为${7*7}返回的预览页面中出现了49确认SSTI。白盒分析查看代码发现其逻辑是根据templateId从数据库读取模板字符串然后用String.replace将模板中的${content}占位符替换为用户传入的content最后调用FreeMarker渲染。这里犯了两个致命错误一是替换操作不严谨用户输入可能包含其他${}结构二是Configuration配置了ALL_CLASSES_TEMPLATE_RESOLVER。漏洞利用直接注入${java.lang.RuntimegetRuntime().exec(touch /tmp/poc)}成功在服务器创建文件。深度利用进一步利用通过执行命令发现服务器是Linux具有Python环境。写入一个简单的HTTP反向Shell脚本并执行成功获取服务器控制权。根本原因不安全的字符串替换逻辑。极度危险的FreeMarker配置。缺乏输入验证和输出编码。修复方案将模板内容中的变量替换改为通过Map构建数据模型让FreeMarker引擎安全渲染。将ClassResolver改为SAFER_RESOLVER并禁用?api。对用户输入的content进行严格的HTML转义因为其最终是在浏览器中显示的内容。这个案例清晰地展示了一个简单的功能点由于不安全的设计和配置如何演变成一个导致服务器沦陷的高危漏洞。它也印证了纵深防护的必要性安全配置、安全编码、运行时监控缺一不可。7. 未来展望与持续学习模板注入漏洞不会消失只会随着框架的演进而变得更加隐蔽。对于FreeMarker我们需要持续关注新版本的安全特性关注FreeMarker官方发布的新版本中是否引入了更严格的默认配置、新的安全API或更好的沙箱机制。新型利用链的披露关注安全社区如Seebug、先知、国外安全博客的最新研究学习新型的绕过技术和利用链。与云原生环境的结合在Kubernetes、Service Mesh环境下如何对模板渲染服务进行更细粒度的网络策略控制和行为监控。自动化审计工具的进化学习使用更先进的代码分析工具如CodeQL编写更精准的规则来自动化发现深层次的漏洞模式。安全是一个持续对抗的过程。作为防御者我们唯有比攻击者更了解系统的每一处细节更早地发现潜在的风险并构建起层层递进的防御体系才能真正守护应用的安全。这份指南是一个起点而非终点。真正的安全能力建立在一次次实战审计、漏洞分析和方案落地的经验积累之上。