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.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
的实现,如图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更改配置,为漏洞利用创造了前提。