Solr-RCE-via-Velocity-template

0x01 环境搭建

solr通过启动的时候加上-a参数,就可以使用额外的 JVM 参数(例如以 -X 开头的参数)启动 Solr,下面开启一下jdwp的远程调试。

1
solr start -p 8988 -f -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8988"

0x02 漏洞分析

可以看到这个 filter 的注释说的,也就说相关路径都会先发送到这个 SolrRequestFilter 进行处理。

image-20191101110359541

选择在 SolrRequestFilterdofilter 处下个断点,可以看到请求过来了。

image-20191101111107240

dofilter 方法中,这里会先获取我们 web.xml 中的 excludePatterns 的路径进行正则比对。

image-20191101111433616

代码继续下行,来到这里,跟进 getHttpSolrCall 方法。

image-20191101111809469

getHttpSolrCall 方法中最后的处理结果是返回一个 HttpSolrCall 对象。

image-20191101111849839

紧接着调用call方法,来到了 HttpSolrCall#call 中。

image-20191101112254719

跟进 HttpSolrCall#call 中,一直往下走,代码来到了这里,根据 switch 进行选择,这里的 actionPROCESS ,所以自然进入 PROCESScase 中进行处理,然后调用this.execute方法来处理 solrRsp 对象。

image-20191101113208651

跟进 HttpSolrCall#execute 中,首先调用 getCore 方法获取 SolrCore 对象,然后调用 SolrCore#execute 方法。

image-20191101125756233

image-20191101130025369

跟进 SolrCore#execute 方法选择在 handler.handleRequest 处下一个断点,为什么会在这里下断点,因为从方法名称来看,这个名称大概会处理我们传入的 request 请求,这里由于我们请求的路径是/solr/test/config实际上是针对这个 config 进行操作,所以这里的handler对象是 SolrConfigHandler

image-20191101131203969

image-20191101131426105

代码继续下行,来到 RequestHandlerBase 类中,这个类调用this.handleRequestBody来处理。

image-20191101131537440

而这个 handlerRequestBody 实际上是一个抽象类,也就是solr实际通过自己的路由分发,将不同url请求,转发到不同的 handler 进行处理,这是我的理解,可能存在偏差。

image-20191101131624562

然后在 SolrConfigHandler#handleRequestBody 中就可以看到一些处理了,由于我们请求的数据类型是json,请求方式POST,所以自然会进入command.handlerPOST进行处理。

image-20191101131829120

跟进command.handlerPOST,实际上可以看到this.handleCommands方法进行处理的时候overlay对象的值正是我们已经传入的。

image-20191101132119845

继续跟进 SolrConfigHandler$commandhandleCommands 方法,这里通过 SolrResourceLoader 类加载资源配置,然后调用 SolrResourceLoader#persistConfLocally 方法针对文件进行操作。

image-20191101132629107

继续 SolrResourceLoader#persistConfLocally 方法可以看到,获取配置文件路径,写入内容,而写入部分的正是我们通过POST方式上传上来的数据。

image-20191101133123753

image-20191101133051025

第一阶段修改配置文件的调用栈如下所示:

1
2
3
4
5
6
7
8
9
10
11
persistConfLocally:900, SolrResourceLoader (org.apache.solr.core)
handleCommands:504, SolrConfigHandler$Command (org.apache.solr.handler)
handlePOST:345, SolrConfigHandler$Command (org.apache.solr.handler)
access$100:158, SolrConfigHandler$Command (org.apache.solr.handler)
handleRequestBody:136, SolrConfigHandler (org.apache.solr.handler)
handleRequest:199, RequestHandlerBase (org.apache.solr.handler)
execute:2551, SolrCore (org.apache.solr.core)
execute:711, HttpSolrCall (org.apache.solr.servlet)
call:516, HttpSolrCall (org.apache.solr.servlet)
doFilter:395, SolrDispatchFilter (org.apache.solr.servlet)
doFilter:341, SolrDispatchFilter (org.apache.solr.servlet)

当然第二阶段就是通过模版注入,远程代码执行,代码断点还是下到call.call位置。

image-20191101151615890

跟进 HttpSolrCall#call 中,跟进下图代码中的this.getResponseWriter

image-20191101151657503

HttpSolrCall#getResponseWriter,可以看到实际上循环来到了下图位置,调用的是 SolrCore#getQueryResponseWriter

image-20191101151858441

SolrCore#getQueryResponseWriter 实际上是根据请求参数重的 wt 字段的值去获取reponseWriter 对象,payload中的参数是 velocity ,所以这里最后的返回对象是 VelocityResponseWriter

image-20191101152152178

image-20191101152245922

紧接着代码下行来到下图位置,我们看到 writeResponse 方法处理了我们刚刚的 VelocityResponseWriter对象。

image-20191101152405122

继续跟进 HttpSolrCall#writeResponse ,代码调用了 writeQueryResponse 方法。

image-20191101152929239

代码一路下行,会来到 VelocityResponseWriter#write 当中,然而刚开始时候,我只导入了solr-webapp目录下 WEB-INF 中的jar文件,然后就会出现下图中的情况,明明debug断点到了,但是无法打开查看源代码。后面深入看看才发现少导入了两个文件的jar,一个是 dist 目录中的 jar 文件,另一个是contrib/velocity/lib目录下的 Jar 文件。

image-20191101230118336

继续愉快的debug,跟进 VelocityResponseWriter#write ,首先调用 createEngine 函数处理我们传入的 request 对象。

image-20191102170526555

跟进 createEngine 函数,这里实例化 SolrParamResourceLoader 类来处理 request 对象。

image-20191102171456353

这里有个疑问为啥 paramsResourceLoaderEnabled 是true,本质原因就在这之前HttpSolrCall#call方法中通过 getResponseWriter 方法,进一步来到 VelocityResponseWriter#init 方法获取到我们之前第一步修改配置文件的时候写入配置文件中的params.resource.loader.enabledsolr.resource.loader.enabled的结果,所以这里自然是true。

image-20191102173417270

image-20191102173541742

跟进 SolrParamResourceLoader 类,这里遍历循环我们payload中的数据,当 namev.template ,我们在payload中的 v.template 的值是 custom ,所以这里实际上是生成了 custom.vm 的恶意 engine

image-20191102171647636

代码回到 createEngine 方法中,这里自然 paramsResourceLoaderEnabled 是true,原因不在细说,上面所过了。

image-20191102173939106

执行完这两个if之中的 SolrParamResourceLoaderSolrVelocityResourceLoader 操作之后,代码回到 VelocityResponseWriter#write 中,调用 VelocityResponseWriter#getTemplate 方法处理刚刚的engine对象,以及我们的request请求对象。

image-20191102174151390

VelocityResponseWriter#getTemplate 方法中回根据我们提交的 v.template 参数获取我们构造的恶意 template 对象。

image-20191102174443779

最后在调用我们构造好的恶意 templatemerge 方法,达到命令模版注入的效果。

image-20191102174833505

实际上模版进入到 templatemerge 方法,然后及时一些AST解析过程。最后补上核心调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)

0x03 流程图

简单绘制一个流程图,方便自己后记

保存

0x04 后话

从solr官方网站可以看出,这个插件实际上是一个可选项,这个漏洞本质还是配合solr未授权来达到rce的目的,所以实际上临时解决这个漏洞最优雅的方式应该是加上鉴权。

image-20191103113924308

加上鉴权啥问题都没得。

image-20191103114046520

Reference

用Intellij idea搭建solr调试环境

[http://lucene.apache.org/solr/guide/6_6/velocity-response-writer.html](