Apache Solr简介

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

Apache Velocity简介

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

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

漏洞复现

前置条件:
1.Solr控制台未设置鉴权(默认),或登录凭证被猜出,这样就能访问到Config API。
2.一个core(索引库)对应一个solrconfig.xml。当某个core(索引库)的solrconfig.xml使用了以下配置,才会受该漏洞影响。

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

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

漏洞复现:

第一步

设置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": "true",
        "params.resource.loader.enabled": "true"
      }
}

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

{"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的实现,如图7,加载了java.lang.Runtime类。

图7

执行了java.lang.Runtime类的exec方法。如图8,实现命令执行。

图8

此时的调用栈:

exec:347, Runtime (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:506, UberspectImpl$VelMethodImpl (org.apache.velocity.util.introspection)
invoke:494, UberspectImpl$VelMethodImpl (org.apache.velocity.util.introspection)
execute:198, ASTMethod (org.apache.velocity.runtime.parser.node)
execute:304, ASTReference (org.apache.velocity.runtime.parser.node)
value:605, ASTReference (org.apache.velocity.runtime.parser.node)
value:72, ASTExpression (org.apache.velocity.runtime.parser.node)
render:235, ASTSetDirective (org.apache.velocity.runtime.parser.node)
render:377, SimpleNode (org.apache.velocity.runtime.parser.node)
merge:359, Template (org.apache.velocity)
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)

修复方案

1.设置鉴权:为Apache Solr设置web鉴权(强口令),避免通过发送请求到ConfigAPI实现修改配置。

参考 Basic Authentication Plugin | Apache Solr Reference Guide 8.2

2.根本解决:不使用这个自带的可选库(删除对应索引库文件夹下的solrconfig.xml中与Velocity相关的内容,删除configoverlay.json)

总结

本次的 Apache Solr 模版注入漏洞,第一步是通过ConfigAPI更改配置。

上次的漏洞分析 - Apache Solr远程代码执行漏洞(CVE-2019-0193)其中一种“漏洞检测”方式第一步也是通过ConfigAPI更改配置。

Apache Solr的Config API是自带功能,用于通过HTTP请求更改配置;当Solr未设置访问鉴权时,可以直接通过ConfigAPI更改配置,为漏洞利用创造了前提。

点击收藏 | 0 关注 | 1 打赏
  • 动动手指,沙发就是你的了!
登录 后跟帖