当前位置: 首页 > news >正文

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)。数据模型通常是一个MapJavaBean,包含了所有模板中可访问的变量。模板中通过${expression}<#directive>来引用和操作这些变量。

漏洞产生的关键点在于,FreeMarker的表达式语言非常强大。它不仅能进行简单的属性取值(${user.name}),还能进行方法调用(${object.method()})、内建函数处理(${value?api.someMethod()})以及访问静态类(${@com.example.SomeClass@METHOD})。当攻击者能够控制expression的内容时,危险便产生了。

2.2 漏洞入口点:从显式到隐式

早期的FreeMarker SSTI案例多集中于开发者显式调用API构造模板的场景:

  1. 直接构造模板new Template("name", new StringReader(templateContent), cfg),其中templateContent用户可控。
  2. 加载模板文件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和挖掘深层次利用链上发展:

  1. ?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”等关键词的简单过滤。
  2. 利用Spring上下文:在Spring MVC集成FreeMarker的应用中,模板自动拥有Spring应用上下文的访问能力。攻击者可能尝试通过${applicationContext}${springMacroRequestContext}等隐含对象来访问Bean,进而执行代码。
  3. 无数字字母的混淆技术:通过FreeMarker的内建函数(如?c(字符串转字符)、?number、集合操作等)构造出所需的字符,再拼接成恶意类名和方法名,以绕过内容安全策略。
  4. 针对ClassResolver的绕过:即使配置了相对安全的ClassResolver,也可能因白名单过宽或解析逻辑缺陷被绕过。攻击者会系统性地探测可用的类。

注意:在审计时,不要只盯着“执行命令”。模板注入可能导致敏感信息泄露(通过访问${configuration}获取数据库配置)、服务器文件读取、甚至作为跳板进行内网横向移动。

3. 漏洞挖掘实战:从黑盒到白盒

漏洞挖掘是一个系统性的过程,需要结合黑盒测试的直觉和白盒审计的精准。

3.1 黑盒模糊测试与手工探测

当面对一个未知应用时,我们可以通过以下步骤进行探测:

  1. 识别FreeMarker使用痕迹

    • 观察HTTP响应头,寻找ServerX-Powered-By等字段是否包含FreeMarker相关信息。
    • 触发错误页面,看是否包含FreeMarkerTemplateProcessingException等关键字。
    • 检查静态资源引用或页面注释中是否有.ftl文件路径。
  2. 注入点探测

    • 参数测试:对所有用户输入点(GET/POST参数、Cookie、Header)尝试注入FreeMarker表达式。最简单的探测payload如${7*7},观察返回是否为49。更隐蔽的可以用${“freemarker”?api.class},看是否返回class freemarker.template.utility.Execute或触发错误。
    • 上下文识别:如果${7*7}被原样输出,尝试#{7*7}(旧版本语法)或<#assign x=7*7>${x}。有时应用会过滤${但遗漏其他语法。
    • 错误信息利用:故意注入语法错误的payload,如${,观察返回的错误信息,这能极大帮助确认漏洞存在和了解环境。
  3. 利用链构造

    • 一旦确认注入点,开始构造利用链。一个通用的测试思路是:
      • 第一步:获取类对象。${“a”?api.class}${object.class}
      • 第二步:遍历类加载器或寻找Runtime。${@java.lang.Class@forName(“java.lang.Runtime”)}
      • 第三步:反射调用方法。利用?api.method()或内建函数?new?constructor等。
    • 使用现成的工具辅助,如tplmap(需适配FreeMarker语法)或Burp Suite的FreeMarker SSTI Scanner插件,但绝不能完全依赖工具,手工理解至关重要。

3.2 白盒代码审计:关键代码模式

白盒审计能更精准地定位问题。你需要重点关注以下几类代码模式:

  1. 模板引擎初始化代码:查找Configuration的实例化和配置位置。重点检查setTemplateLoadersetSharedVariable、以及最重要的setNewBuiltinClassResolversetAPIBuiltinEnabled

    // 危险配置示例 Configuration cfg = new Configuration(Configuration.VERSION_2_3_31); cfg.setNewBuiltinClassResolver(TemplateClassResolver.ALL_CLASSES_TEMPLATE_RESOLVER); // 允许所有类访问 cfg.setAPIBuiltinEnabled(true); // 启用?api内建函数,风险增高
  2. 模板渲染调用点

    • template.process(dataModel, writer)
    • Template类的getTemplatecreateTemplate
    • Spring的FreeMarkerViewResolver相关配置,检查视图名是否用户可控。
  3. 用户输入流入数据模型的路径

    • 搜索model.addAttribute()model.put()ModelAndView.addObject()等调用,追溯其参数来源,是否直接来自HttpServletRequest
    • 检查自定义的TemplateDirectiveModelTemplateMethodModelEx实现,其executeexec方法的参数是否用户可控且未过滤。
  4. 表达式动态求值

    • 全局搜索?eval?interpretEnvironmenteval/interpret方法。这是最高危的模式之一。
    // 高危代码示例 String userExpr = request.getParameter("expr"); TemplateModel result = env.eval(userExpr); // 直接求值用户输入

3.3 审计工具链与辅助脚本

纯人工审计效率低,需要借助工具:

  • 静态分析工具(SAST):使用SemgrepCodeQL编写自定义规则来捕捉上述危险模式。例如,编写规则查找cfg.setNewBuiltinClassResolver调用且参数为不安全常量的情况。
  • IDE全局搜索:利用IntelliJ IDEA或Eclipse强大的搜索功能,对关键词(如“Template”、“?eval”、“ClassResolver”)进行跨项目搜索。
  • 自定义Gadget探测脚本:编写一个简单的Java程序,模拟目标应用的FreeMarker配置,然后自动尝试加载一系列已知的危险类(如freemarker.template.utility.Executejava.lang.ProcessBuilder),测试其是否在ClassResolver的白名单/黑名单之外。

实操心得:在白盒审计中,我习惯先快速通读web.xml或Spring Boot的启动类,找到FreeMarker配置类的位置。这就像拿到了地图的“图例”,能让你快速理解整个应用的模板安全基线。然后,再以“数据流”的方式,从Controller层接收参数的地方开始,跟踪数据如何被放入Model,最终在哪个.ftl文件中被以何种方式引用。这条路径往往就是漏洞链。

4. 漏洞利用链深度解析与构造

理解漏洞原理后,我们需要掌握如何构造有效的利用链。这里分几个层次讲解。

4.1 基础利用链:命令执行与文件读取

假设我们已经有一个可执行任意表达式的注入点,并且ClassResolver配置宽松。

  1. 通过freemarker.template.utility.Execute执行命令: 这是FreeMarker内置的一个危险类。如果它未被禁止,利用起来最简单。

    <#assign ex="freemarker.template.utility.Execute"?new()> ${ ex("open -a Calculator") }

    或者使用?api链:

    ${"freemarker.template.utility.Execute"?api.newInstance()("calc.exe")}
  2. 通过Java反射调用Runtime: 这是更通用的方法,不依赖FreeMarker特定类。

    <#assign rt=@java.lang.Runtime@getRuntime()> ${ rt.exec("whoami") }

    或者:

    ${@java.lang.Class@forName("java.lang.Runtime").getMethod("getRuntime").invoke(null).exec("id")}
  3. 文件读取: 利用java.io.FileInputStreamjava.util.Scannerorg.apache.commons.io.IOUtils

    <#assign is=@java.io.FileInputStream@new("/etc/passwd")> <#assign sc=@java.util.Scanner@new(is).useDelimiter("\\A")> ${ sc.hasNext() ? sc.next() : "" }

4.2 绕过受限环境:当?api和危险类被禁用

在实际安全配置中,管理员通常会禁用?api和黑名单类。这时需要更巧妙的绕过。

  1. 利用ObjectWrapperBeansWrapper: FreeMarker在包装对象时,可能会暴露一些getter方法。如果数据模型中有一个ServletRequest对象(Spring中可能是request),你可以尝试${request.servletContext}来获取ServletContext,进而可能找到其他有用的Bean或属性。

  2. 利用Spring的隐含变量: 在Spring MVC环境中,即使FreeMarker配置严格,Spring也可能自动向模型添加一些对象。尝试访问以下变量:

    • ${springMacroRequestContext}:可能暴露应用上下文信息。
    • ${request}${session}${application}(对应ServletContext)。
    • 通过${request.getServletContext().getAttributeNames()}来枚举属性,寻找有用的类实例。
  3. 字符构造与混淆: 如果表达式被执行但返回结果被过滤或转义,可以通过构造字符来拼接命令。

    <#assign a="a"?api.class.forName("java.la"+"ng.Ru"+"ntime")>

    或者利用数字转字符:${(65)?char}得到A

4.3 高级利用:从SSTI到内存马与横向移动

在攻防演练中,模板注入的价值远不止于弹个计算器。

  1. 写入Webshell: 如果能执行命令且有写权限,可以直接写文件。更优雅的方式是利用FreeMarker的TemplateLoader。如果存在FileTemplateLoader且路径可控,可以尝试将恶意模板内容写入一个.ftl文件,然后通过访问该模板的URL来触发执行。

  2. 注入内存马: 通过执行Java代码,可以利用Java Agent技术或直接使用工具类(如java.lang.instrument.Instrumentation)动态修改已加载类的字节码,注入Filter型或Controller型内存马。这需要攻击者对Java内存马技术有深入理解,并且目标环境具备相应条件(如开启-javaagent参数或存在可利用的组件)。

  3. 作为内网探测跳板: 利用Runtime执行命令,可以调用curlnslookupping等命令对内网进行探测。或者,利用Java网络库(java.net.HttpURLConnection)直接发起HTTP请求,扫描内网服务。

注意事项:在实战利用时,务必考虑命令回显的问题。如果页面不回显执行结果,你需要考虑使用curlwget将结果外带(DNSLog、HTTP请求),或者写入一个临时文件再从Web路径访问。同时,注意命令的跨平台兼容性(Linux vs Windows)。

5. 零信任架构下的纵深防护方案

“零信任”的核心思想是“从不信任,始终验证”。将这一理念应用于FreeMarker模板注入的防护,意味着我们不能只依赖边界WAF或简单的黑名单,而需要在应用生命周期的各个层面建立防御。

5.1 安全开发层:代码与配置加固

这是最根本的一层,需要在开发阶段就植入安全基因。

  1. 严格的FreeMarker配置

    • 禁用?api:除非业务绝对需要,否则在Configuration中设置cfg.setAPIBuiltinEnabled(false)。这是关闭高风险功能的最有效方式。
    • 使用安全的ClassResolver:绝对不要使用ALL_CLASSES_TEMPLATE_RESOLVER。推荐使用TemplateClassResolver.SAFER_RESOLVER(FreeMarker 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); } });
    • 设置模板加载路径:使用FileTemplateLoaderClassTemplateLoader时,将其根目录限制在安全的、仅包含模板文件的目录下,防止目录穿越。
  2. 安全的编码实践

    • 输入净化与输出编码:所有用户输入在放入数据模型前,必须根据其在模板中的使用场景进行净化。如果作为文本显示,进行HTML编码;如果作为模板的一部分,必须严格验证其内容,禁止包含FreeMarker指令标签(<#,${,[#,[=等)。
    • 避免动态模板/表达式求值:彻底杜绝使用?eval?interpretEnvironment.eval()。任何需要动态逻辑的地方,都应该在Java代码中实现,然后将结果传递给模板。
    • 最小权限数据模型:不要将整个HttpServletRequestServletContext对象放入数据模型。只传递模板渲染所必需的最小数据集。

5.2 运行时防护层:RASP与动态检测

在应用运行时提供额外的保护。

  1. 应用运行时自我保护(RASP)

    • 部署RASP Agent,它可以注入到FreeMarker的核心类(如TemplateEnvironment)中,在模板解析和渲染的关键节点进行钩子(Hook)。
    • 检测逻辑:监控TemplateClassResolver的解析行为,对尝试加载的类名进行实时分析,阻断对java.lang.ProcessBuilderfreemarker.template.utility.Execute等危险类的加载。监控?api的调用栈,识别异常的反射调用链。
    • RASP的优势在于能结合应用上下文进行精准判断,误报率低,且能防御未知攻击手法。
  2. Web应用防火墙(WAF)增强

    • 传统的基于正则表达式的WAF规则(如检测Runtimeexec)很容易被混淆绕过。现代WAF应结合语义分析,能够解析FreeMarker表达式的基本结构,识别出嵌套的方法调用、反射等危险模式。
    • 可以与RASP联动,WAF作为第一道粗筛,将可疑请求标记并传递给RASP进行深度分析。

5.3 资产与流程管控层:零信任的落地

零信任要求对访问主体、资源、请求进行持续验证。

  1. 微服务间模板调用的身份认证:如果存在A服务调用B服务的模板渲染接口,必须使用强身份认证(如mTLS、JWT),确保只有合法的服务才能发起请求,防止内部接口被滥用。
  2. 动态权限与访问策略:对于管理后台等可以上传或修改模板的功能,实施基于角色的访问控制(RBAC)和属性基访问控制(ABAC)。记录所有模板修改操作,并定期进行安全审计。
  3. 持续安全监控与审计
    • 集中收集所有应用日志,特别是FreeMarker抛出的TemplateException。异常的模板解析错误可能是攻击尝试的信号。
    • 建立针对模板渲染接口的流量基线,监控异常访问模式(如大量携带特殊参数的请求、来自异常IP的访问)。
    • 定期进行代码审计和渗透测试,将FreeMarker SSTI作为必查项。

5.4 供应链安全:第三方依赖管理

FreeMarker本身作为依赖,其安全性也至关重要。

  1. 持续更新:及时将FreeMarker升级到最新稳定版,以获取安全修复。关注其安全公告。
  2. SCA工具扫描:使用软件成分分析(SCA)工具,持续监控项目中所有依赖(包括FreeMarker及其传递依赖)是否存在已知漏洞(CVE)。
  3. 自定义模板指令/函数的安全审查:对业务中自行开发的TemplateDirectiveModel或自定义函数进行严格的安全代码审查,确保其内部逻辑安全。

6. 企业级审计流程与实战案例复盘

将上述所有点串联起来,形成一套可重复的企业内部审计流程。

6.1 四阶段审计流程

  1. 信息收集阶段

    • 识别应用技术栈:通过构建脚本、组件清单、接口文档确认是否使用FreeMarker。
    • 梳理模板使用场景:列出所有涉及模板渲染的URL接口、后台功能点(如邮件模板、报告生成、页面渲染)。
    • 获取代码权限:申请对应代码仓库的只读权限。
  2. 静态代码分析阶段

    • 使用SAST工具进行全量扫描,生成初步报告。
    • 人工复核SAST报告,并重点审计Configuration配置类、所有Controller中向Model添加数据的逻辑、以及全局搜索?eval等危险函数。
    • 绘制关键数据流图,标记从用户输入到模板渲染的完整路径。
  3. 动态验证与利用阶段

    • 根据白盒分析结果,在测试环境构造对应的HTTP请求进行验证。
    • 使用手工payload和工具进行模糊测试,尝试触发异常或执行成功。
    • 验证漏洞危害程度,尝试构造文件读取、命令执行等利用链。
  4. 报告与修复建议阶段

    • 编写详细漏洞报告,包含漏洞位置、触发条件、利用方式、风险等级、修复建议(提供安全的代码样例)。
    • 推动开发团队修复,并建议将安全的FreeMarker配置和编码规范纳入开发手册。

6.2 实战案例复盘:某OA系统模板注入漏洞

背景:在对某企业旧版OA系统进行授权测试时,发现其公文预览功能存在异常。

过程

  1. 黑盒探测:发现预览接口接收一个templateId参数和一个content参数。修改content${7*7},返回的预览页面中出现了49,确认SSTI。
  2. 白盒分析:查看代码,发现其逻辑是:根据templateId从数据库读取模板字符串,然后用String.replace将模板中的${content}占位符替换为用户传入的content,最后调用FreeMarker渲染。这里犯了两个致命错误:一是替换操作不严谨,用户输入可能包含其他${}结构;二是Configuration配置了ALL_CLASSES_TEMPLATE_RESOLVER
  3. 漏洞利用:直接注入${@java.lang.Runtime@getRuntime().exec("touch /tmp/poc")},成功在服务器创建文件。
  4. 深度利用:进一步利用,通过执行命令发现服务器是Linux,具有Python环境。写入一个简单的HTTP反向Shell脚本,并执行,成功获取服务器控制权。

根本原因

  1. 不安全的字符串替换逻辑。
  2. 极度危险的FreeMarker配置。
  3. 缺乏输入验证和输出编码。

修复方案

  1. 将模板内容中的变量替换,改为通过Map构建数据模型,让FreeMarker引擎安全渲染。
  2. ClassResolver改为SAFER_RESOLVER,并禁用?api
  3. 对用户输入的content进行严格的HTML转义,因为其最终是在浏览器中显示的内容。

这个案例清晰地展示了,一个简单的功能点,由于不安全的设计和配置,如何演变成一个导致服务器沦陷的高危漏洞。它也印证了纵深防护的必要性:安全配置、安全编码、运行时监控缺一不可。

7. 未来展望与持续学习

模板注入漏洞不会消失,只会随着框架的演进而变得更加隐蔽。对于FreeMarker,我们需要持续关注:

  • 新版本的安全特性:关注FreeMarker官方发布的新版本中,是否引入了更严格的默认配置、新的安全API或更好的沙箱机制。
  • 新型利用链的披露:关注安全社区(如Seebug、先知、国外安全博客)的最新研究,学习新型的绕过技术和利用链。
  • 与云原生环境的结合:在Kubernetes、Service Mesh环境下,如何对模板渲染服务进行更细粒度的网络策略控制和行为监控。
  • 自动化审计工具的进化:学习使用更先进的代码分析工具(如CodeQL),编写更精准的规则来自动化发现深层次的漏洞模式。

安全是一个持续对抗的过程。作为防御者,我们唯有比攻击者更了解系统的每一处细节,更早地发现潜在的风险,并构建起层层递进的防御体系,才能真正守护应用的安全。这份指南是一个起点,而非终点。真正的安全能力,建立在一次次实战审计、漏洞分析和方案落地的经验积累之上。

http://www.jsqmd.com/news/1104703/

相关文章:

  • 在 Ubuntu 26.04 上安装 Docker CE 教程
  • 3步解锁Microsoft 365完整功能:终极免费Office激活钩子工具
  • 铜钟音乐:构建纯净听歌体验的终极免费音乐平台完整指南
  • 【AI】55个AI基础概念(收藏版)
  • Rust AI 命令行工具:从参数解析到模型调用的最小闭环
  • Firefox自动化测试环境搭建与WebDriver配置完全指南
  • 告别Selenium:5分钟用Playwright录制Web自动化测试脚本
  • JMeter SSH Sampler性能测试插件:原理、配置与实战应用
  • 让 AI Agent 学会收发邮件:Agent Mail CLI 配置体验与玩法
  • 【Java从入门到精通】第9篇:继承的威力——extends、super与方法重写的多态根基
  • 揭秘暗黑3技能连点器:7个智能游戏辅助技巧彻底改变你的战斗体验
  • 网络寻踪进阶:数字调查人员的开源情报(OSINT)全功能工具箱
  • 网易云发布ai歌曲
  • 免费音乐解锁工具终极指南:一键解密QQ音乐、网易云等加密格式
  • Jetson TK1时区与时间配置实战指南
  • 探索macOS Catalina Patcher:让老旧Mac焕发新生的完整技术指南
  • 广东芬隆科技快速熔断器技术解析与应用指南
  • Token工厂崛起:AI算力底座从“资源供给”向“生产范式”跃迁的观察
  • 解Bug之路-Nginx 502 Bad Gateway
  • 向量数据库选型与实战指南:5分钟上手 Milvus,打造智能语义搜索
  • Server 可观测性集成:OpenTelemetry 埋点、结构化日志与审计流水线
  • 每天浪费2小时?用taskt桌面自动化工具解放你的双手
  • Pwn2Own事件后QNAP NAS紧急安全修复与深度防护指南
  • 从XSSed实战到系统防御:一次存储型XSS漏洞的应急响应与加固全记录
  • Counterfeit-V3.0:如何突破AI绘画的构图限制?
  • JMeter性能测试入门:从环境搭建到实战脚本与结果分析
  • hpcpilot源码解读:10个核心脚本实现原理与架构设计揭秘
  • CVE-2024-50623漏洞复现:润乾报表InputServlet任意文件读取深度解析
  • 3步解决微信QQ语音播放难题:Silk-V3-Decoder音频转换全攻略
  • [特殊字符] 我昨天下午说巴西2-1日本,今天凌晨一看,真是这比分