39

Solr-RCE-via-Velocity-template

 4 years ago
source link: https://www.tuicool.com/articles/iERJr2Y
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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

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

0x02 漏洞分析

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

rMzYRnb.png!web

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

faUVBjz.png!web

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

eyEjquN.png!web

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

QjEF7fI.png!web

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

Rb6Ff2e.png!web

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

7vQrU3A.png!web

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

nEnIvuE.png!web

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

MRRVZzR.png!web

RJJV3eY.png!web

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

NB32UvJ.png!web

3UFNbmZ.png!web

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

6Z3QZvZ.png!web

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

Eb6Bbi2.png!web

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

bI7FVnN.png!web

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

aemMJfZ.png!web

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

2IBvumB.png!web

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

ZreE73Z.png!web

EV3uYnq.png!web

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

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 位置。

EjYR3eF.png!web

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

yme2uyE.png!web

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

YVraM3R.png!web

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

MNF322Y.png!web

QJRzaqn.png!web

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

bUFn6b3.png!web

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

BzAbuum.png!web

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

qaMZJna.png!web

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

N3AjIjr.png!web

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

ja6ZbiJ.png!web

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

BJ7NjeF.png!web

IBBFn2I.png!web

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

rymIFnv.png!web

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

NvQn63U.png!web

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

URZziiE.png!web

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

36nAbiZ.png!web

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

UJVz6f3.png!web

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

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 流程图

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

neQvQb7.png!web

0x04 后话

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

VjaM3em.png!web

加上鉴权啥问题都没得。

V77Z7rq.png!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK