漏洞分析 - Apache Solr 模版注入漏洞(RCE)
2019-11-09 12:21:53 Author: xz.aliyun.com(查看原文) 阅读量:273 收藏

参考资料

Config API | Apache Solr Reference Guide 8.2

国外的安全研究员S00pY在GitHub发布的poc

Apache Solr简介

Apache Solr是一个企业级搜索平台,用Java编写且开源,基于Apache Lucene项目。

更多参考漏洞分析 - Apache Solr远程代码执行漏洞(CVE-2019-0193) - 先知社区

Apache Velocity简介

据Apache介绍,Velocity 是基于Java的模版引擎(template engine)

  • 作用概括如下
    • Web开发:基于 Model-View-Controller (MVC)模型开发时,Velocity模版引擎可作为view(视图)引擎,将Java代码与网页分开,可用于取代JSP。
    • 非Web领域:可用作生成源代码和报告的独立实用程序,也可以用作其他系统的集成组件。

漏洞复现

一个core(索引库)对应一个solrconfig.xml

前置条件:
1.Solr控制台可被直接访问(默认未设置鉴权)
2.如果某个core(索引库)的solrconfig.xml有如下配置,才会受该漏洞影响。

<queryResponseWriter name="velocity" class="solr.VelocityResponseWriter" startup="lazy">
 <!-- something  -->
  </queryResponseWriter>

漏洞复现:

第一步

设置VelocityResponseWriter插件的params.resource.loader.enabled选项设置为true

Apache Solr默认带有VelocityResponseWriter插件,该插件的params.resource.loader.enabled选项(默认为false),用来控制是否允许resource.loader在Solr请求参数中指定模版。

以下HTTP请求会设置VelocityResponseWriter插件的params.resource.loader.enabled选项设置为true,即允许用户通过HTTP请求指定资源的加载。

POST /solr/core_name/config HTTP/1.1
Host: solr.com:8983
Content-Type: application/json
Content-Length: 293

{
      "update-queryresponsewriter": {
        "startup": "lazy",
        "name": "velocity",
        "class": "solr.VelocityResponseWriter",
        "template.base.dir": "",
        "solr.resource.loader.enabled": "false",
        "params.resource.loader.enabled": "false"
      }
}

测试发现,HTTP/1.1 200 OK则修改成功。并新建了/core_name/conf/configoverlay.json文件,内容如下

{"queryResponseWriter":{"velocity":{
      "startup":"lazy",
      "name":"velocity",
      "class":"solr.VelocityResponseWriter",
      "template.base.dir":"",
      "solr.resource.loader.enabled":"true",
      "params.resource.loader.enabled":"true"}}}

HTTP Response 状态码为404,则修改选项失败(常常因为这个core对应的solrconfig.xml没配置VelocityResponseWriter插件)

第二步

构造一个自定义的Velocity模版,可实现执行任意系统命令

这里执行了ls -a

漏洞分析

参考静态代码,进行动态调试。

  • 对图片中HTTP请求中参数的解释:
    • 参数wt - 输出结果格式,通常为json/xml等格式,如果设置值为velocity 则会通过velocity引擎解析
    • 参数v.template - 模版名称,我设置模版名称为template1
    • 参数v.template.template1 - 自定义模板template1 的具体内容

从HTTP请求开始追踪。

找到/solr-8.2.0/dist/solr-core-8.2.0.jar!/org/apache/solr/handler/RequestHandlerBase.class类的handleRequest方法,下断点可以跟踪HTTP请求(输入的数据)是如何被处理的。

跟到/solr-8.2.0/dist/solr-core-8.2.0.jar!/org/apache/solr/servlet/HttpSolrCall.classwriteResponse方法开始跟,如图0,可看到GET请求的完整URL

图0

继续跟。

当前位置:/solr-8.2.0/dist/solr-core-8.2.0.jar!/org/apache/solr/response/QueryResponseWriterUtil.class

看到QueryResponseWriterUtil类的writeQueryResponse方法

图1

关键语句:
responseWriter.write(writer, solrRequest, solrResponse);

跟进。

当前位置:/solr-8.2.0/dist/solr-velocity-8.2.0.jar!/org/apache/solr/response/VelocityResponseWriter.class

如图2,是VelocityResponseWriter类的write方法的方法体。

图2 VelocityResponseWriter类的write方法的方法体

ps:比较重要的VelocityResponseWriter类的所有方法如下,重点关注VelocityResponseWriter类的write方法、createEngine方法

VelocityResponseWriter类的所有方法

关键语句:看到write方法的方法体中第一行有createEngine方法。

跟进。

如图3,是VelocityResponseWriter类的createEngine方法的方法体。

图3

看下方法体里的语句:

首先,new了一个名为engineVelocityEngine对象(Velocity引擎)。

然后看if语句1

// 因为之前通过ConfigAPI开启了两个选项,所以这里2个if条件都会满足

// if语句1
// 功能:从HTTP请求中获取参数 如模版名称
if (this.paramsResourceLoaderEnabled) {
    loaders.add("params");
    engine.setProperty("params.resource.loader.instance", new SolrParamResourceLoader(request));//具体看下这个实现
}

if语句1中,看到类SolrParamResourceLoader

跟到 /solr-8.2.0/dist/solr-velocity-8.2.0.jar!/org/apache/solr/response/SolrParamResourceLoader.class
执行逻辑:找到HTTP请求中指定模版的参数名(此时为v.template.templatename1),并将对应的“模版内容“这一value对应到hashmaptemplates中名为v.template.templatename1的key。(图4)

图4 类SolrParamResourceLoader

重新回到createEngine方法的方法体,如图3。

看if语句2

// if语句2
// 功能:设置velocity引擎的solr.resource.loader.instance属性
if (this.solrResourceLoaderEnabled) {
    loaders.add("solr");
    engine.setProperty("solr.resource.loader.instance", new SolrVelocityResourceLoader(request.getCore().getSolrConfig().getResourceLoader()));
}

看到类SolrVelocityResourceLoader

具体逻辑:根据本次HTTP请求中的具体索引库名称(core_name)和它的配置信息(solrconfig.xml)得到一个SolrVelocityResourceLoader对象(加载了Velocity引擎解析所必要的资源),设置velocity引擎属性solr.resource.loader.instance的值为这个SolrVelocityResourceLoader对象。(图5)

图5 类SolrVelocityResourceLoader

重新回到createEngine方法的方法体,如图3。

createEngine方法的方法体执行结束,返回一个名为engineVelocityEngine对象。

重新回到VelocityResponseWriter类的write方法的方法体,如图2。

继续执行下一条语句,调用当前VelocityResponseWriter对象的getTemplate方法。

跟进VelocityResponseWriter类的getTemplate方法,具体实现如图6,该方法的作用是从HTTP请求中读取参数,得到模版名称、模版内容,最后返回模版对象。

图6 VelocityResponseWriter类的getTemplate方法的具体实现

重新回到VelocityResponseWriter类的write方法的方法体,如图2。
得到了一个名为templateTemplate类型的对象。

继续执行,下一条语句,调用当前VelocityResponseWriter对象的createContext方法,创建了一个名为contentVelocityContext类型的对象(存放了模版内容)。

继续执行,
看到关键语句template.merge(context, writer);

此时调用栈

merge:264, Template (org.apache.velocity)
write:166, VelocityResponseWriter (org.apache.solr.response)
writeQueryResponse:65, QueryResponseWriterUtil (org.apache.solr.response)
writeResponse:873, HttpSolrCall (org.apache.solr.servlet)
call:582, HttpSolrCall (org.apache.solr.servlet)
doFilter:423, SolrDispatchFilter (org.apache.solr.servlet)
doFilter:350, SolrDispatchFilter (org.apache.solr.servlet)
doFilter:1602, ServletHandler$CachedChain (org.eclipse.jetty.servlet)
doHandle:540, ServletHandler (org.eclipse.jetty.servlet)
handle:146, ScopedHandler (org.eclipse.jetty.server.handler)
handle:548, SecurityHandler (org.eclipse.jetty.security)
handle:132, HandlerWrapper (org.eclipse.jetty.server.handler)
nextHandle:257, ScopedHandler (org.eclipse.jetty.server.handler)
doHandle:1711, SessionHandler (org.eclipse.jetty.server.session)
nextHandle:255, ScopedHandler (org.eclipse.jetty.server.handler)
doHandle:1347, ContextHandler (org.eclipse.jetty.server.handler)
nextScope:203, ScopedHandler (org.eclipse.jetty.server.handler)
doScope:480, ServletHandler (org.eclipse.jetty.servlet)
doScope:1678, SessionHandler (org.eclipse.jetty.server.session)
nextScope:201, ScopedHandler (org.eclipse.jetty.server.handler)
doScope:1249, ContextHandler (org.eclipse.jetty.server.handler)
handle:144, ScopedHandler (org.eclipse.jetty.server.handler)
handle:220, ContextHandlerCollection (org.eclipse.jetty.server.handler)
handle:152, HandlerCollection (org.eclipse.jetty.server.handler)
handle:132, HandlerWrapper (org.eclipse.jetty.server.handler)
handle:335, RewriteHandler (org.eclipse.jetty.rewrite.handler)
handle:132, HandlerWrapper (org.eclipse.jetty.server.handler)
handle:505, Server (org.eclipse.jetty.server)
handle:370, HttpChannel (org.eclipse.jetty.server)
onFillable:267, HttpConnection (org.eclipse.jetty.server)
succeeded:305, AbstractConnection$ReadCallback (org.eclipse.jetty.io)
fillable:103, FillInterest (org.eclipse.jetty.io)
run:117, ChannelEndPoint$2 (org.eclipse.jetty.io)
runTask:333, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
doProduce:310, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
tryProduce:168, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
run:126, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy)
run:366, ReservedThreadExecutor$ReservedThread (org.eclipse.jetty.util.thread)
runJob:781, QueuedThreadPool (org.eclipse.jetty.util.thread)
run:917, QueuedThreadPool$Runner (org.eclipse.jetty.util.thread)
run:748, Thread (java.lang)

force step in 进入merge的实现,如图7,加载了类java.lang.Runtime,之后执行了exec方法,实现了命令执行。

图7

修复方案

1.为Apache Solr增加web鉴权,避免通过发送请求到ConfigAPI实现修改配置。
2.不使用这个自带的可选库(删除对应索引库文件夹下的solrconfig.xml中与Velocity相关的内容,删除configoverlay.json)


文章来源: http://xz.aliyun.com/t/6700
如有侵权请联系:admin#unsafe.sh