Config API | Apache Solr Reference Guide 8.2
国外的安全研究员S00pY在GitHub发布的poc
Apache Solr是一个企业级搜索平台,用Java编写且开源,基于Apache Lucene项目。
更多参考漏洞分析 - Apache Solr远程代码执行漏洞(CVE-2019-0193) - 先知社区
据Apache介绍,Velocity 是基于Java的模版引擎(template engine)
一个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请求开始追踪。
找到/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.class
的writeResponse
方法开始跟,如图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了一个名为engine
的VelocityEngine
对象(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
方法的方法体执行结束,返回一个名为engine
的VelocityEngine
对象。
重新回到VelocityResponseWriter
类的write
方法的方法体,如图2。
继续执行下一条语句,调用当前VelocityResponseWriter
对象的getTemplate
方法。
跟进VelocityResponseWriter
类的getTemplate
方法,具体实现如图6,该方法的作用是从HTTP请求中读取参数,得到模版名称、模版内容,最后返回模版对象。
图6
VelocityResponseWriter
类的getTemplate
方法的具体实现
重新回到VelocityResponseWriter
类的write
方法的方法体,如图2。
得到了一个名为template
的Template
类型的对象。
继续执行,下一条语句,调用当前VelocityResponseWriter
对象的createContext
方法,创建了一个名为content
的VelocityContext
类型的对象(存放了模版内容)。
继续执行,
看到关键语句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)