本文目录导读:

我来介绍Java中常见的模板注入(SSTI)案例和漏洞分析。
FreeMarker模板注入
漏洞示例
// 不安全的FreeMarker使用
@PostMapping("/render")
public String renderTemplate(String template) throws Exception {
Template t = new Template("user", template, new Configuration());
return processTemplate(t);
}
// 攻击payload
${7*7}
<#assign value="freemarker.template.utility.Execute"?new()>${value("id")}
<#assign value="freemarker.template.utility.ObjectConstructor"?new()>${value("java.lang.ProcessBuilder","id")}
安全修复
// 安全的FreeMarker配置
Configuration cfg = new Configuration();
cfg.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER);
cfg.setObjectWrapper(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_31).build());
// 或使用白名单
class WhitelistResolver implements TemplateClassResolver {
private static final Set<String> WHITELIST = Set.of("java.lang.String");
@Override
public Class resolve(String name, Environment env) throws TemplateException {
if (!WHITELIST.contains(name)) {
throw new InvalidReferenceException("Class " + name + " not allowed");
}
return ClassUtil.forName(name);
}
}
Thymeleaf模板注入
漏洞示例
// 不安全的Thymeleaf使用
@GetMapping("/hello")
public String hello(@RequestParam String name) {
return "Hello " + name + "!"; // 模板名称来自用户输入
}
// 攻击payload
__$%7Bnew%20java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('id').getInputStream()).useDelimiter('\\A').next()%7D__::.x
__${T(java.lang.Runtime).getRuntime().exec("id")}__::.x
安全修复
// 使用参数化模板
@GetMapping("/hello")
public String hello(@RequestParam String name, Model model) {
model.addAttribute("name", name);
return "hello"; // 模板名称固定
}
// 如果没有配置视图解析器,直接返回JSON
@ResponseBody
public Map<String, String> hello(@RequestParam String name) {
return Map.of("message", "Hello " + name);
}
Velocity模板注入
漏洞示例
// 不安全的Velocity使用
Velocity.init();
VelocityContext context = new VelocityContext();
context.put("name", userInput);
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "test", templateContent); // 模板内容来自用户输入
// 攻击payload
#set($e="e")
$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("id")
#set($s="")
#set($rt=$s.getClass().forName("java.lang.Runtime"))
#set($ex=$rt.getMethod("getRuntime",null).invoke(null,null))
$ex.exec("id")
安全修复
// 安全的Velocity配置
VelocityEngine engine = new VelocityEngine();
engine.setProperty(RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS, "org.apache.velocity.runtime.log.Log4JLogChute");
engine.setProperty("velocimacro.library", ""); // 禁止宏加载
engine.setProperty("resource.loader", "string"); // 只允许字符串模板
engine.setProperty("runtime.strict_mode.enable", true);
engine.setProperty("event_handler.reference_insertion.class", "SafeReferenceHandler");
// 启动安全检查
engine.init();
JSP EL注入
漏洞示例
// 不安全的JSP EL使用
<%
String expression = request.getParameter("expression");
javax.el.ExpressionFactory factory =
javax.el.ExpressionFactory.newInstance();
javax.el.ValueExpression ve = factory.createValueExpression(
pageContext.getELContext(), "${" + expression + "}", Object.class);
out.println(ve.getValue(pageContext.getELContext()));
%>
// 攻击payload
${pageContext.servletContext.classLoader.getResource("").toURI()}
${pageContext.request.getSession().setAttribute("x", "y")}
安全修复
// 使用安全的EL处理器
import javax.el.ELProcessor;
public class SafeELProcessor {
private static final Set<String> BLOCKED =
Set.of("getClass", "forName", "getRuntime", "exec");
public Object evaluate(String expression) {
// 添加输入验证
for (String blocked : BLOCKED) {
if (expression.contains(blocked)) {
throw new SecurityException("Blocked: " + blocked);
}
}
ELProcessor elp = new ELProcessor();
elp.getELManager().addELResolver(new BeanELResolver(false)); // 禁用反射
return elp.eval(expression);
}
}
Pebble模板注入
漏洞示例
// 不安全的Pebble使用
PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate compiledTemplate = engine.getTemplate("inline", new StringReader(template));
// 攻击payload
{{ variable.getClass().forName("java.lang.Runtime").getRuntime().exec("id") }}
{% set str = variable.getClass().forName("java.lang.Runtime").getRuntime().exec("id") %}
安全修复
// 安全的Pebble配置
PebbleEngine engine = new PebbleEngine.Builder()
.extension(new SandboxExtension()) // 使用沙箱
.autoEscaping(true)
.build();
// 自定义沙箱扩展
public class SandboxExtension extends AbstractExtension {
@Override
public Map<String, Function> getFunctions() {
return Map.of(
"include", new InternalFunction("include"),
"extends", new InternalFunction("extends")
);
}
}
漏洞检测方法论
基础检测payload
// 数学运算测试
${7*7}
#{7*7}
{{7*7}}
// 字符串操作测试
${'a' + 'b'}
<%="abc"%>
// 系统属性探测
${system.properties}
${pageContext}
危险函数检测
// 常见危险函数
T(java.lang.Runtime).getRuntime().exec()
"freemarker.template.utility.Execute"?new()
pageContext.getClass().forName("java.lang.Runtime")
防御措施总结
输入验证与过滤
public class TemplateSecurityUtil {
private static final Pattern DANGEROUS_PATTERN = Pattern.compile(
"(?i).*(runtime|processbuilder|scriptengine|classloader|reflection|invoke).*"
);
public static boolean isSafeTemplate(String template) {
if (template == null || template.trim().isEmpty()) {
return false;
}
// 基础检查
if (template.length() > 1000 || DANGEROUS_PATTERN.matcher(template).matches()) {
return false;
}
return true;
}
}
使用Ognl安全配置
// OGNL安全配置 OgnlContext context = (OgnlContext) Ognl.createDefaultContext(new Object()); context.setMemberAccess(new DefaultMemberAccess(false)); // 禁用私有成员访问
最佳实践
- 永远不要直接拼接用户输入到模板
- 使用参数化模板引擎
- 配置严格的安全策略
- 使用白名单而非黑名单
- 定期更新模板引擎版本
- 实施纵深防御策略
这些案例展示了Java模板注入的常见类型和防御方法,实际应用中应根据具体场景选择合适的防护措施。