0x01 漏洞描述
Solr 是Apache软件基金会开源的搜索引擎框架,其中定义的ConfigAPI允许设置任意的jmx.serviceUrl,它将创建一个新的JMXConnectorServerFactory工厂类实例对象,并通过对目标RMI / LDAP服务器的’bind’操作触发调用。 远程恶意RMI服务器可以响应任意的对象,Solr端使用java中ObjectInputStream类将接收到的对象进行不安全的反序列化过程。使用ysoserial工具可以利用此类漏洞,根据目标Classpath环境,攻击者可以使用其中特定的“gadget chains”在Solr端触发远程代码执行。
风险等级:High
影响版本:5.0.0 to 5.5.5 6.0.0 to 6.6.5
0x02 漏洞分析
Solr初始化及启动过程:研究solr启动过程,首先需要从War包中加载的配置文件web.xml入手,其中所有的访问请求统一路由到SolrDispatchFilter类中处理,其主要的作用包括加载solr配置文件、初始化各个core、初始化各个requestHandler和component:
查看了该类的继承结构:
BaseSolrFilter,是一个实现Filter接口的抽象类,功能很简单,就是判断当前程序是否开启正常的日志记录功能:
作为具体类的SolrDispatchFilter必须实现Filter接口中定义的如下三个抽象方法:
其中doFilter方法拦截所有的http请求,下面跟进下SolrDispatchFilter类中的doFilter具体方法:
首先定位到用于处理请求数据的接口SolrRequestHandler,其中定义的抽象方法handleRequest中分别传入了用于处理请求的接口SolrQueryRequest和处理响应的接口SolrQueryResponse,在IDEA中查看接口的实现类,发现只有抽象类RequestHandlerBase实现了上述接口:
跟进抽象类RequestHandlerBase,其重写了抽象方法handleRequest,并在其中调用了handleRequestBody抽象方法:
为进一步了解handleRequestBody方法的实现过程,根据继承关系图发现,SolrConfigHandler具体类继承了抽象类RequestHandlerBase:
进一步跟进SolrConfigHandler具体类,发现其重写了上述的handleRequestBody抽象方法,并在方法体中首先判断HTTP请求参数是否为POST,若一致则调用command.handlePOST()方法:
根据公开POC表明漏洞利用过程是通过POST方式触发,则进一步跟进command.handlePOST()方法:
跟进其调用的handleCommands(opsCopy, overlay)方法,其中使用switch函数对传入的方法名做了选择判断,SET_PROPERTY常量字符串对应于set-property方法:
由于公开POC显示在构造恶意Json数据时使用了set-property属性:
所以继续跟进上图case分支中的applySetProp(op, overlay)方法:
跟进setProperty方法,方法体中将POST参数转换后,调用有参类型的ConfigOverlay构方法生成新的ConfigOverlay对象,方法调用结束后通过return函数将其返回到上文调用方法handleCommands的overlay变量中:
上文handleCommands方法在接收到正常的overlay返回值后,调用break子句结束了for循环遍历操作符过程,继续向下执行:
Solr资源加载器通过当前请求对象的getCore().getResourceLoader()方法创建了新的加载器对象loader。通过if条件判断loader是否为zookeeper分布式集群ZkSolrResourceLoader类的实例对象。受本地环境限制,此时会执行else代码块中的单机模式。继续跟进persistConfLocally(loader, ConfigOverlay.RESOURCE_NAME, overlay.toByteArray())方法,其中传入的实参RESOURCE_NAME是如下常量字符串:
调用File类的构造函数创建confFile实例对象,并通过OutputStream输出流中的write方法将configoverlay.json文件内容在磁盘中做持久化存储:
反向追踪RESOURCE_NAME常量的调用过程,发现SolrConfig类加载了该配置文件:
为了清晰的了解Solr容器的启动过程,需要从org/apache/solr/core/CoreContainer类中定义的reload(String name)方法入手:
其中传递的参数String name是需要重新加载的SolrCore,当重新加载时,其所属的配置文件也将从新加载,继续跟进getConfig方法:
其中调用的createSolrConfig方法用于重新加载配置文件,跟进SolrConfig类结构,发现其定义了重载的构造方法:
当非初始化时,调用getOverlay()方法:
跟进getConfigOverlay方法,其资源加载器确实加载了上文中定义的配置文件,并和上述的方法调用过程相吻合:
继续执行构造函数,通过JmxConfiguration构造函数接收用户传递参数并赋值给jmxConfig对象:
逆向追踪jmxConfig调用过程:
发现SolrCore中通过if条件对是否开启JMX做了判断,若为false,则通过JmxMonitoredMap构造方法返回一个新的对象:
跟进JmxMonitoredMap有参构造函数,其中通过JMXConnectorServerFactory工厂类创建了JMXConnectorServer类的实例对象并调用start()方法向远程RMI服务器发送请求:
当获取远程RMI服务器恶意序列化对象后的反序列化调用链可以参考ysoserial的7u21的漏洞利用过程。
0x03 漏洞利用
- 靶机中开启Solr服务,并监听本地8983端口:
- 攻击机使用ysoserial攻击创建RMI服务并监听1099端口:
java -cp ysoserial-master-ff59523eb6-1.jar ysoserial.exploit.JRMPListener 1099 Jdk7u “touch /tmp/pwn.txt”
- 攻击机发送POC请求,Solr端返回了500状态码:
- 攻击机监听端口收到回连请求,并成功在远程靶机上执行系统命令创建文件:
0x04 修复方案
* 升级到Apache Solr 7.0或更高版本。
* 通过设置系统属性值 “disable.configEdit=true”禁用未被使用的ConfigAPI。
* 若上述升级或禁用Config API不是可行的解决方案,请下载安全公告[1]中的补丁SOLR-13301.patch并重新编译Solr,在新发布的补丁中发现已经禁用了JMX中的serviceUrl属性:
* 确保配置了正确的网络访问控制策略设置,只允许受信任的流量进入或退出运行Solr服务的主机。
0x05 Reference
[1] https://issues.apache.org/jira/browse/SOLR-13301
[2] https://wiki.apache.org/solr/SolrSecurity
[4] https://lucene.apache.org/solr/guide/6_6/config-api.html#ConfigAPI-CommandsforCommonProperties