Apache Solr Velocity模板注入漏洞分析
Nov 05, 2019
1 minute read

0x01 概述

10月30日,研究员S00pY在GitHub发布了Apache Solr Velocity模版注入远程命令执行的poc,该漏洞通过设置资源加载属性,利用VelocityResponseWriter插件执行自定义模板,进而进行远程代码执行,危害较大,下面是分析过程。

0x02 环境搭建

选择Solr 8.2.0二进制版本进行分析和复现

下载地址:https://archive.apache.org/dist/lucene/solr/8.2.0/

调试命令

$ cd solr-8.2.0\server
$ java "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000" -Dsolr.solr.home="../example/example-DIH/solr/" -jar start.jar --module=http

IDEA新建远程调试即可

0x03 前置概念

VelocityResponseWriter(Velocity响应编写器)是 contrib/velocity 目录中可用的可选插件。当使用诸如 “_default”、“techproducts” 和 “example / files” 等配置时,它为浏览用户界面提供动力。

必须添加它的 JAR 和依赖项(通过或 solr/home lib 包含),并且必须在 solrconfig.xml 注册,默认已经注册

1572507926900

其中有一个属性params.resource.loader.enabled,默认是false,需要手动开启

该参数表示允许加载程序在 Solr 请求参数中指定模板,例如:

http://localhost:8983/solr/gettingstarted/select?q=\*:*&wt=velocity&v.template=xxx&v.template.xxx=CUSTOM%3A%20%23core_name

v.template=xxx表示创建一个名为“xxx”的模板,v.template.xxx则是模板内容

当这个属性设置为true时用户就可以传入任意模板内容进行模板注入,从而执行任意命令

0x04 漏洞分析

设置params.resource.loader.enabled属性

在Solr的Web.xml文件中能看到所有的请求都交给org.apache.solr.servlet.SolrDispatchFilter来处理

1572508606062

具体的则是其中的doFilter()方法。在对路由经过初步处理后,进行两个关键操作:

HttpSolrCall call = this.getHttpSolrCall(request, response, retry);
//...
SolrDispatchFilter.Action result = call.call();

初始化一个HttpSolrCall对象后调用它的call()方法,在call()方法中会对路由中具体的组件初始化出对应的handler,再由这个handler去调用这个组件的各个方法

在Solr 8.2.0中具体的路由有37个,每一类都有对应的handler,都在org.apache.solr.handler中定义,例如solr/solr/get对应的hendler为RealTimeGetHandler

1572515458181

1572516210537

/solr/solr/configSolrConfigHandler来分别处理GET和POST请求

SolrConfigHandler.Command command = new SolrConfigHandler.Command(req, rsp, httpMethod);
if ("POST".equals(httpMethod)) {
    if (configEditing_disabled || this.isImmutableConfigSet) {
        String reason = configEditing_disabled ? "due to disable.configEdit" : "because ConfigSet is immutable";
        throw new SolrException(ErrorCode.FORBIDDEN, " solrconfig editing is not enabled " + reason);
    }

    try {
        command.handlePOST();
    } finally {
        RequestHandlerUtils.addExperimentalFormatWarning(rsp);
    }
} else {
    command.handleGET();
}

私有类Command会对当前路由的webapp和path做一个切分,对于POST请求,分别会通过SolrConfigHandler.Command#handlePOST()方法来处理

1572509660264

接着调用SolrConfigHandler.Command#handleCommands(),Solr中Config API对应的实现都是由这个方法来完成的,如set-propertyunset-property

1572516461804

此处主要关注更新配置的参数

文档可以了解对于responsewriter的操作有下面三个

  • add-queryresponsewriter
  • update-queryresponsewriter
  • delete-queryresponsewriter

1572516621585

代码中也能看到对操作名称按-进行分割提取出对应操作,然后由updateNamedPlugin()方法来完成配置文件的创建/覆盖操作,具体跟入看一下

updateNamedPlugin()中有个verifyClass的调用,当传入参数没有设置runtimeLib时会去创建class字段指定的类,所以当我们传入VelocityResponseWriter时,会在其初始化的时候写入对应的参数

1572518046827

然后返回到handleCommands()中把配置写入到configoverlay.json文件

1572518122216

因此,通过config api可以重新设置VelocityResponseWriter的属性,为下一步加载模板提供入口

三种命令的区别如下:

add- 命令都会将新配置添加到configoverlay.json,这将覆盖solrconfig.xml组件中的任何其他设置; update- 命令覆盖configoverlay.json中的现有设置; delete-命令从configoverlay.json中删除设置

注入自定义模板

SolrDispatchFilter中有有一个枚举类Action,定义了每个handler的所属的操作,通过ConfigAPI更新配置时,当前的action是PROCESS,因此会进入HttpSolrCall.call()PROCESS分支

1572576900618

之后通过QueryResponseWriterUtil.writeQueryResponse()进入VelocityResponseWriter.write,在这个方法中完成Velocity的解析

1572577848829

首先会初始化一个解析模板的引擎VelocityEngine,在创建引擎的过程中会检查是否允许参数资源加载,这也就是第一个请求设置的params.resource.loader.enabled属性值。由于solr.resource.loader.enabled默认是开启的,所以此处只需要设置params的值

1572577993537

之后通过Template.getTemplate()设置自定义模板,然后进入Template.merge()进入AST解析,在解析过程中会调用到ASTMethod.execute()方法,这个流程与之前披露的CVE-2019-11581 JIRA模板注入漏洞是一样的,不再赘述,详细可以参考CVE-2019-11581 ATLASSIAN JIRA 未授权模板注入漏洞分析

回过头看一下Velocity渲染的大致流程:

Velocity 渲染引擎首先磁盘加载模板文件到内存,然后解析模板模板文件为 AST 结构,并对 AST 中每个节点进行初始化,第二次加载同一个模板文件时候如果开启了缓存则直接返回模板资源,通过使用资源缓存节省了从磁盘加载并重新解析为 AST 的开销。

ASTMethod.execute()方法设计之初是在Velocity parse解析模板的过程中,通过反射调用相关方法完成正常模板渲染动作,例如获取背景颜色、获取 text 内容、获取页面编码等,但当此处攻击者传入精心构造的数据后,利用反射执行了java.lang.Runtime.getRuntime,成功达到命令执行的目的

调用栈

1572575433979

PoC

1572578779708

参考


Back to posts


comments powered by Disqus