flawopen.com/SSTI/FreeMarker 与 Velocity
FreeMarker 和 Velocity 模板可以安全处理用户输入吗?
用户提供的值被插入可信模板时仍是数据。用户控制模板源代码则不同:用户可以编写模板语言表达式。表达式可访问的能力取决于版本、对象包装器或 introspector、模型对象和应用权限。不要假设每次模板注入都会执行操作系统命令;应把不可信模板源视为可执行逻辑,并在应用进程之外渲染。
// 风险:将请求数据解析为模板源代码 String src = "Hello " + userSuppliedName; Template t = new Template("t", src, cfg); t.process(model, writer); // 风险:求值用户控制的 VTL 字符串 Velocity.evaluate(context, writer, "log", userTemplate);
// 可信 classpath 模板;模型仅包含标量值 Configuration cfg = new Configuration( Configuration.VERSION_2_3_34); cfg.setTemplateLoader(new ClassTemplateLoader( App.class, "/templates")); cfg.setNewBuiltinClassResolver( TemplateClassResolver.ALLOWS_NOTHING_RESOLVER); cfg.setObjectWrapper(new SimpleObjectWrapper( Configuration.VERSION_2_3_34)); Template t = cfg.getTemplate("hello.ftl"); Map<String,Object> model = Map.of( "name", userSuppliedName); t.process(model, writer); // Velocity:可信模板与受限 introspection(纵深防御) Properties p = new Properties(); p.setProperty("resource.loaders", "class"); p.setProperty("resource.loader.class.class", ClasspathResourceLoader.class.getName()); p.setProperty("runtime.introspector.uberspect", SecureUberspector.class.getName()); VelocityEngine engine = new VelocityEngine(); engine.init(p); Template vt = engine.getTemplate("hello.vm", "UTF-8"); VelocityContext vc = new VelocityContext(); vc.put("name", userSuppliedName); vt.merge(vc, writer);
为什么 Java 模板引擎可能暴露危险能力
模板会基于数据模型计算表达式,但可访问的 Java 范围取决于配置。FreeMarker 默认对象包装器可能公开任意 POJO 的 JavaBean 属性和方法;实际成员取决于包装器和访问策略。Velocity 默认 Uberspect 会将 VTL 属性映射到公开 Java 方法。这不代表任意模型都能无限制使用反射:若模型包含服务对象、类加载器、请求对象或其他强大能力,风险会升高。优先使用字符串映射和简单值,只向可信模板提供所需字段。
FreeMarker 的内置函数 ?new 可以实例化实现 TemplateModel 的类。Apache 建议对上传的模板限制类解析;SAFER_RESOLVER 仍允许部分类,而 ALLOWS_NOTHING_RESOLVER 会拒绝所有类解析。该控制不能代替受限对象包装器和最小化数据模型。
降低风险并了解控制边界
- ✓FreeMarker:处理不可信模板时,将
new_builtin_class_resolver设为ALLOWS_NOTHING_RESOLVER,并使用会拒绝任意 POJO 的SimpleObjectWrapper等包装器。只传入简单且用途明确的值。 - ✓Velocity:不可信模板使用
SecureUberspector,上下文中只放必要的简单值。它会阻止文档记录的反射和对象创建路径,但不是操作系统沙箱。 - ✓两者:从可信且防篡改的来源加载应用模板。绝不要把请求参数或用户可修改的数据库行转换为模板源代码。
- ✓如果产品确实需要用户编写模板:在独立、最小权限进程中渲染,禁止网络、机密和应用可写文件;在模板引擎之外设置 CPU、内存和运行时间上限。
将引擎限制视为纵深防御,而非进程隔离。使用受支持版本,按照引擎当前安全指南核对实际配置,并避免向渲染器提供机密或特权对象。
常见问题
这是相关但不同的风险。用户可控的视图名称本身并不自动构成表达式注入。应检查数据是否实际流入 Thymeleaf 或 Spring Expression Language 的表达式求值,并避免使用不可信输入构造表达式片段。保持集成组件更新,并通过可信应用逻辑选择视图。
模板作者可以使用引擎和数据模型公开的能力,但这不一定意味着能执行操作系统命令。应将模板编辑权限视为敏感的代码编写权限,只授予可信维护人员并审计变更。
Mustache 这类无逻辑模板引擎可以减少表达式和方法调用面,但输出编码、模板来源控制和 helper 暴露的能力仍然重要。应选择适合该信任模型的引擎并隔离渲染;不要把通用 JVM 引擎当作沙箱。