漏洞描述
Apache Solr是美国阿帕奇(Apache)软件基金会的一款基于Lucene(一款全文搜索引擎)的搜索服务器。该产品支持层面搜索、垂直搜索、高亮显示搜索结果等。
Apache Solr的DataImportHandler是一个可选但常用的模块,可从数据库(通过JDBC)、RSS、Web 页面和文件中导入数据。而且这个模块的配置文件不仅可以在服务端中通过配置文件指定,也可以从用户请求的dataConfig中获取。
The entire configuration itself can be passed as a request parameter using the dataConfig parameter rather than using a file.
而为了对数据进行转换,这个dataConfig中可以包含脚本。在Apache Solr < 8.2.0 的版本中, DataImportHandler的dataConfig参数为用户可控,攻击者可通过构造恶意的dataConfig脚本交由转换器(Transformers)进行解析,而Solr在解析的过程中并未对用户输入的脚本进行检查,导致攻击者可在Solr服务器上执行任意代码。
漏洞利用的前提条件是Solr有一个具有dataimport功能的core,这个功能需要在这个core对应的solrconfig.xml
配置文件中指定requestHandler
节点的class属性为solr.DataImportHandler
,
且Solr未开启认证(默认未开启认证),在这种情况下,Solr Admin UI上的操作是不需要登录凭据的。
开启认证可通过编辑配置文件:server/solr-webapp/webapp/WEB-INF/web.xml
参考:https://brandnewuser.iteye.com/blog/2318027
复现环境搭建
使用Solr 8.1.1进行复现。
复现环境中,配置文件内容如下,没有配置任何dataSource:
代码追踪
关键调用栈为:
|
从org.apache.solr.core.SolrCore#execute
开始,
用户指定的dataConfig传入DataImportHandler
。
在org.apache.solr.handler.dataimport.DataImportHandler#handleRequestBody
中,
判断command参数,这里我们的请求是full-import
,所以进入到这个else if中
在
|
中,将用户输入的dataConfig内容传到DataImporter对象的私有成员变量config(DIHConfiguration
)中(后续会用到)。
若用户指定了debug为true,执行
|
用户没指定debug为true也没关系,会在后面的else if逻辑中,
在新线程执行runCmd方法。
在DataImporter#runCmd
中,判断command为full-import
之后,执行
|
关于Solr的delta-import和full-import功能参考:
http://www.zhongruitech.com/4016598944.html
继续跟进doFullImport,
接下来的过程是:
|
其中在DocBuilder#buildDocument
过程中,需要对EntityProcessorWrapper
进行初始化EntityProcessorWrapper#init
。
在初始化的过程中,从Context里获取dataSource,
在DataImporter中,从DIHConfiguration中取DataSource:
拿到DataSource的类名之后,使用DocBuilder.loadClass
载入这个DataSource类。在SolrResourceLoader#findClass
中,由于我们提供的URLDataSource不是全限定名,这里需要从一个列表中遍历查找待载入的全限定名的DataSource类,通过反射载入我们的URLDataSource类,然后新建其实例。
若未指定dataSource或者未找到dataSource,则会在日志中记录这个异常,无法执行我们的payload。
然后到了向url发起请求取HTTP数据的步骤,
在URLDataSource#getData
中,
由于访问url的过程中,存在默认的HTTP超时时间,超时后,会抛出异常,无法执行payload。为了避免访问URL的时候出现超时导致无法执行后续步骤的情况,这里可以将url中的RSS换成一个国内的源。
向用户指定的document的entity节点中的url发起请求。
从
|
这一行出来之后,
调用
|
PS:看方法名字,大概意思是应用这个转换器,这里应该就是对成功拿到数据之后做的操作了。 跟进一下,
先载入转换器,在loadTransformers()
中
若指定的转换器名以script:
开头,则意味着使用脚本转换器(否则略过这个if流程,直接进入下面执行指定转换器的流程),则将script:
后面的函数名取出,并将这个函数名设置为脚本转换器(ScriptTransformer
)要执行的函数名。
脚本转换器允许使用Java支持的语言(比如Javascript, JRuby, Jython, Groovy, or BeanShell)编写任意转换函数,其中Javascript语言已默认集成在Java中了,使用其他语言的话需要自己整合。
每个转换函数都
必须接收一个row变量(与Java中的Map类型对应,所以是支持get、put、remove等操作的),函数的功能是修改已知field的值,或者添加新的fields。处理完之后,将row对象作为返回值返回。
这个脚本会以最高级别插入到DIH配置文件中,每个row会调用一次。
在载入ScriptTransformer
这个转换器之后,
执行
在52行,先初始化脚本引擎initEngine(context)
。
在initEngine
方法中,会从context中取出脚本的语言,和具体代码。这里执行这段js代码,由于这里只是js的函数定义,所以并没有真正执行在函数中的payload。
在55行,最后调用javax.script.Invocable#invokeFunction
执行我们指定的poc函数。
Demo
PoC
这个PoC是从一个RSS站点取数据,然后取到之后调用poc函数,使用js调用java方法。关于使用js调用java参考:
https://www.ibm.com/support/knowledgecenter/en/SSHS8R_8.0.0/com.ibm.worklight.dev.doc/devref/t_calling_java_code_from_a_javas.html
这里指定处理器(processor)为XPathEntityProcessor
,这个类是对抽象类EntityProcessor
的一个实现,根据访问url得到的xml内容,通过xpath解析器从xml文档中抽取需要的内容。它通常与URLDataSource
或FileDataSource
结合使用。而可用的DataSource有以下几种:
- ContentStreamDataSource
- FieldReaderDataSource
- FileDataSource
- JdbcDataSource
- URLDataSource
如果使用JdbcDataSource
,需要jdbc驱动,且需要提供一个登录数据库的用户名密码。于是我们选择了不需要额外凭据的URLDataSource
作为DataSource,配合XPathEntityProcessor
,
The XPathEntityprocessor is designed to stream the xml, row by row (Think of a row as various fields in a xml element ). It uses the forEach attribute to identify a ‘row’.
从前面的分析来看,既然只是看transformer中的值是否以script:
开头,那么其实可以这样写poc,直接在script节点里调用js代码就好了,不用写成一个函数。另外发现其实还有几个不必要的参数,又精简了一下poc如下:
|
然后这个core的名字new_core
视实际情况而定。需要发送一次请求:http://cqq.com:8983/solr/admin/cores
获取。
漏洞修复
官方commit中,
https://github.com/apache/lucene-solr/commit/325824cd391c8e71f36f17d687f52344e50e9715
补丁增加了一个Java系统属性enable.dih.dataConfigParam
(默认为false)
只有启动solr的时候加上参数-Denable.dih.dataConfigParam=true 这样enable.dih.dataConfigParam系统属性才为true。
使用Solr 8.2.0验证漏洞修复情况。再发同样的payload,响应403,
因为在DataImportHandler#handleRequestBody
中,抛出了异常。
参考
- [1] https://issues.apache.org/jira/browse/SOLR-13669
- [2] https://cwiki.apache.org/confluence/display/solr/DataImportHandler
- [3] https://github.com/apache/lucene-solr/commit/325824cd391c8e71f36f17d687f52344e50e9715
- [4] https://stackoverflow.com/questions/7081318/solr-dataimporthandler-can-i-get-a-dynamic-field-name-from-xml-attribute-with-x
- [5] http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-201908-031
- [6] https://github.com/apache/lucene-solr/blob/325824cd391c8e71f36f17d687f52344e50e9715/solr/solr-ref-guide/src/uploading-structured-data-store-data-with-the-data-import-handler.adoc