FlawOpen

flawopen.com/SSTI/FreeMarker 与 Velocity

FreeMarker 和 Velocity 模板可以安全处理用户输入吗?

CWE-1336: Server-Side Template Injection参考页面
简短回答

用户提供的值被插入可信模板时仍是数据。用户控制模板源代码则不同:用户可以编写模板语言表达式。表达式可访问的能力取决于版本、对象包装器或 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 会拒绝所有类解析。该控制不能代替受限对象包装器和最小化数据模型。

降低风险并了解控制边界

将引擎限制视为纵深防御,而非进程隔离。使用受支持版本,按照引擎当前安全指南核对实际配置,并避免向渲染器提供机密或特权对象。

常见问题

Thymeleaf 也受影响吗?

这是相关但不同的风险。用户可控的视图名称本身并不自动构成表达式注入。应检查数据是否实际流入 Thymeleaf 或 Spring Expression Language 的表达式求值,并避免使用不可信输入构造表达式片段。保持集成组件更新,并通过可信应用逻辑选择视图。

只有管理员能编辑模板,这样可以吗?

模板作者可以使用引擎和数据模型公开的能力,但这不一定意味着能执行操作系统命令。应将模板编辑权限视为敏感的代码编写权限,只授予可信维护人员并审计变更。

用户提供模板时哪种引擎更安全?

Mustache 这类无逻辑模板引擎可以减少表达式和方法调用面,但输出编码、模板来源控制和 helper 暴露的能力仍然重要。应选择适合该信任模型的引擎并隔离渲染;不要把通用 JVM 引擎当作沙箱。

官方来源与参考资料

另请参阅 SSTI:模板注入的成因、影响与修复 Jinja2 的 render_template_string() 安全吗? 什么是服务端模板注入?