漏洞分析 - Apache Solr 模版注入漏洞(RCE)

2019-11-09 约 341 字 预计阅读 2 分钟

声明:本文 【漏洞分析 - Apache Solr 模版注入漏洞(RCE)】 由作者 arr0w1 于 2019-11-09 11:21:53 首发 先知社区 曾经 浏览数 53 次

感谢 arr0w1 的辛苦付出!

参考资料

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)

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now