漏洞介绍
Apache Solr 是一个开源的搜索服务器。Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。此次漏洞出现在Apache Solr的DataImportHandler,该模块是一个可选但常用的模块,用于从数据库和其他源中提取数据。它具有一个功能,其中所有的DIH配置都可以通过外部请求的dataConfig参数来设置。由于DIH配置可以包含脚本,因此攻击者可以通过构造危险的请求,从而造成远程命令执行。
漏洞范围
Apache Solr < 8.2.0 并且开启了DataImportHandler模块(默认情况下该模块不被启用),存在该漏洞。
Solr>=8.2.0版安全。因为从Solr>=8.2.0版开始,默认不可使用dataConfig参数,想使用此参数需要将Java System属性“enable.dih.dataConfigParam”设置为true。只有当Solr>=8.2.0但是主动将Java System属性“enable.dih.dataConfigParam”设置为true,才存在漏洞。
靶场配置
使用vulhub的靶场
环境为Apache solr 8.1.1
/vulhub-master/solr/CVE-2019-0193
1 | 启动靶场:docker-compose up -d |
漏洞原理
此漏洞位于Apache Solr的可选模块DataImportHandler模块中。
模块介绍:
DataImportHandler模块
虽然是一个可选模块,但是它常常被使用。
该模块的作用:从数据源(数据库或其他源)中提取数据。
- 该模块的配置信息 “DIH配置”(DIH configuration) 可使用以下的方式指定:
- Server端 - 通过Server的“配置文件“来指定配置信息”DIH配置”
- web请求 - 使用web请求中的dataConfig参数(该参数用户可控)来指定配置信息”DIH配置”(整个DIH配置可以来自请求的“dataConfig”参数)
Apache Solr如果启用了DataImportHandler模块,因为它支持使用web请求来指定配置信息”DIH配置” ,攻击者可构造HTTP请求指定dataConfig参数的值(dataConfig内容),dataConfig内容完全可控(多种利用方式),后端处理的过程中,可导致命令执行。
Solr在处理dataimport请求时,会进入DataImportHandler的handleRequestBody方法,如果请求的command为full-import,则会重新加载配置。
在加载配置的过程中,如果dataConfig参数不为空,它会通过readFromXml方法解析提交的配置数据中的各个标签,如document、script、function、dataSource等。
ScriptTransformer允许多种脚本语言调用,如JavaScript、JRuby、Jython、Groovy和BeanShell等。在transformRow()方法中,会根据指定的语言初始化对应的解析引擎,例如JavaScript脚本。
Solr中默认的JavaScript引擎是Nashorn,它允许在JavaScript中通过Java.type引用Java类,从而可以执行任意代码
路由分析:Web后端收到URL为 /solr/xxx_core_name/dataimport 的 HTTP请求时,会将HTTP请求实参req传入DataImportHandler类的handleRequestBody方法。
文件位置:/solr-8.1.1/dist/solr-dataimporthandler-8.1.1.jar!/org/apache/solr/handler/dataimport/DataImportHandler.class
关键的类:org.apache.solr.handler.dataimport.DataImportHandler
按command+F12查看 DataImportHandler类的方法 和 成员变量
关键方法:很容易发现,DataImportHandler类的handleRequestBody方法是用于接受HTTP请求的。
在该方法下断点,以便跟踪输入的数据是如何被处理的。(函数体过长 我折叠了部分逻辑)

dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DataImportHandler.class#L80-L167,L143,L145
执行逻辑:从PoC中可见,HTTP请求中有debug=true,根据handleRequestBody方法体中的if-else分支判断逻辑可知,会调用maybeReloadConfiguration方法(功能是重新加载配置)。
关键方法:maybeReloadConfiguration方法
关键语句:this.importer.maybeReloadConfiguration(requestParams, defaultParams);
solr\contrib\dataimporthandler\src\java\org\apache\solr\handler\dataimport\DataImportHandler.java#L138-L158
跟进(Step into)关键方法 maybeReloadConfiguration 方法
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DataImporter.class#L102-L158
关键语句:String dataConfigText = params.getDataConfig();//获取HTTP请求中POST body中的参数dataConfig的值
执行逻辑:maybeReloadConfiguration方法体中,会获取HTTP请求中POST body中的参数dataConfig的值,即“DataConfig配置信息“,如果该值不为空则该值传递给loadDataConfig方法(功能是加载DataConfig配置信息)
本次调试过程中的”DataConfig配置信息”:
1 | <dataConfig> |
解释:<dataSource type="URLDataSource"/>表示数据源的类型为URLDataSource
并且该数据源的实体,属性如下
1 | <entity name="stackoverflow" |
跟进(Step into)关键方法 loadDataConfig方法
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DataImporter.class#L176-L224
执行逻辑:loadDataConfig方法的具体实现中,207行调用了readFromXml方法,从xml数据中读取信息。
关键语句:dihcfg = this.readFromXml(document);
跟进(Step into)关键方法 readFromXml方法
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DataImporter.class#L226-L328
关键语句:return new DIHConfiguration((Element)documentTags.get(0), this, functions, script, dataSources, pw);
执行逻辑:readFromXml方法的具体实现中,根据各种不同名称的标签(如document,script,function,dataSource等),得到了配置数据中的元素。如,配置信息中的自定义脚本在此处被赋值给名为script的Script类型的变量中。使用“迭代器“递归解析完所有标签后,new一个DIHConfiguration对象(传入的6个实参中有个是script变量),这个DIHConfiguration对象作为readFromXml方法的返回值,被return。
该DIHConfiguration对象,实际赋值给了(调用readFromXml方法的) loadDataConfig方法体中的名为dihcfg的变量。(见图3)
回溯:现在的情况是,之前在loadDataConfig方法体中调用了的readFromXml方法已经执行结束并返回了一个DIHConfiguration对象,赋值给了loadDataConfig方法体中的那个名为dihcfg的变量,loadDataConfig方法成功获取到配置信息。
回溯:现在的情况是,之前的maybeReloadConfiguration方法体中调用了的loadDataConfig方法执行结束,DataImporter类的maybeReloadConfiguration方法也得到它需要的boolean返回值,true(见图2)
DataImporter类 org.apache.solr.handler.dataimport.DataImporter
DataImporter类 包含的方法和变量,如图,重点关注的方法是:
1 | maybeReloadConfiguration方法 - boolean maybeReloadConfiguration(RequestInfo params, NamedList<?> defaultParams) |


回溯:现在的情况可参考图0,回到了DataImportHandler类的handleRequestBody方法体中,在该方法体中调用了的(DataImporter类中的)maybeReloadConfiguration方法已经执行结束,继续向下执行到关键语句this.importer.runCmd(requestParams, sw);调用了(DataImporter类中的)runCmd方法
跟进(Step into)关键方法 :(DataImporter类中的)runCmd方法
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DataImporter.class#L485-L508
跟进DataImporter类中的doFullImport方法体
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DataImporter.class#L427-L448
功能如下
首先创建一个DocBuilder对象。
DocBuilder对象的主要功能是从给定配置中创建Solr文档 该对象具有名为config的DIHConfiguration类型的成员变量 见代码 private DIHConfiguration config;
然后调用该DocBuilder对象的execute()方法,作用是使用“迭代器“ this.config.getEntities().iterator();
,解析”DIH配置” 即名为config的DIHConfiguration类型的成员变量,根据“属性名称“(如preImportDeleteQuery、postImportDeleteQuery、)获得Entity的所有属性。
最终得到是一个EntityProcessorWrapper对象。
简单介绍下DocBuilder类。
DocBuilder类 org.apache.solr.handler.dataimport.DocBuilder
DocBuilder类 包含的方法,如下图,重点关注:
1 | execute()方法 - public void execute() |

简单介绍下EntityProcessorWrapper类。
EntityProcessorWrapper类 org.apache.solr.handler.dataimport.EntityProcessorWrapper
EntityProcessorWrapper是一个比较关键的类,继承自EntityProcessor,在整个解析过程中起到重要的作用。
EntityProcessorWrapper类的更多信息参考
https://lucene.apache.org/solr/8_1_1/solr-dataimporthandler/org/apache/solr/handler/dataimport/EntityProcessorWrapper.html
EntityProcessorWrapper类 包含的方法,如下图,重点的是:
loadTransformers()方法 - 作用:加载转换器
在解析完config数据后,solr会把最后“更新时间“记录到配置文件中,这个时间是为了下次进行增量更新的时候用的。
接着通过this.dataImporter.getStatus()判断当前数据导入是“增量导入”即doDelta()方法,还是“全部导入”即doFullDump()方法。
本次调试中的操作是全部导入”,因此调用doFullDump()方法
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DocBuilder.class#L161-L267
跟进DocBuilder类中的doFullDump方法体:
1 | private void doFullDump() { |
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DocBuilder.class#L314-L317
可见,在doFullDump()方法体中,调用的是DocBuilder类中的buildDocument()方法。
作用是为发送的配置数据的每一个Processor做解析(调用getVariableResolver()方法),当发送的entity中含有Transformers时,会进行相应的转换操作。
例如 DateFormatTransformer 转换成日期格式
例如 RegexTransformer 根据正则表达式转换
例如 ScriptTransformer 根据用户自定义的脚本进行数据转换(漏洞关键:脚本内容完全用户可控!!)
等等
具体如何执行JavaScript脚本?继续跟进,DocBuilder类中的buildDocument()方法。
1 | private void buildDocument(VariableResolver vr, DocBuilder.DocWrapper doc, Map<String, Object> pk, EntityProcessorWrapper epw, boolean isRoot, ContextImpl parentCtx, List<EntityProcessorWrapper> entitiesToDestroy) { |
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\DocBuilder.class#L400-L591
可见方法体中,有一行语句是Map<String, Object> arow = epw.nextRow();,功能是“读取EntityProcessorWrapper的每一个元素“,该方法返回的是一个Map对象。
对该语句下断点,进入EntityProcessorWrapper类中的nextRow方法:
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\EntityProcessorWrapper.class#L219-L248
可见,EntityProcessorWrapper类中的nextRow方法体242行中,调用了EntityProcessorWrapper类中的applyTransformer()方法。
继续跟进,EntityProcessorWrapper类中的applyTransformer()方法体:
功能
第1步.调用loadTransformers方法,作用是“加载转换器“
第2步.调用对应的Transformer的transformRow方法
applyTransformer()方法体,代码如下
1 | protected Map<String, Object> applyTransformer(Map<String, Object> row) { |
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\EntityProcessorWrapper.class#L132-L213
第1步.
调用loadTransformers()方法。
查看loadTransformers()方法体,可见它的作用是“加载转换器“:
即如果trans以script:开头,则new一个ScriptTransformer对象。
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\EntityProcessorWrapper.class#L85
loadTransformers()方法体,代码如下
1 | void loadTransformers() { |
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\EntityProcessorWrapper.class#L59-L107
第2步.调用对应的Transformer的transformRow方法
transformRow方法体的执行步骤:
第(1)步.初始化脚本引擎
第(2)步.使用invoke执行脚本
transformRow方法体,代码如下
1 | public Object transformRow(Map<String, Object> row, Context context) { |
dist\solr-dataimporthandler-8.1.1\org\apache\solr\handler\dataimport\ScriptTransformer.class#L21-L34
第(1)步:
transformRow方法体中的语句this.initEngine(context);调用了initEngine方法(该方法只做初始化,并未执行JavaScript脚本)
调试过程中,可查看到initEngine方法中的名为scriptText的String类型的变量,值为:
1 | function poc(){ java.lang.Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator"); |
第(2)步:
调用Nashorn脚本引擎的invokeFunction方法,在Java环境中执行JavaScript脚本:
transformRow方法体中的语句this.engine.invokeFunction(this.functionName, row, context);
附:Nashorn脚本引擎的invokeFunction方法定义:
1 | public Object invokeFunction(String name, Object... args) throws ScriptException, NoSuchMethodException { |
后来发现Solr中的Nashorn脚本引擎的invokeFunction方法(这个能执行JavaScript代码的“值得关注”的方法),其实只在ScriptTransformer类中被调用。
漏洞利用链
- 获取Core信息:攻击者可以通过发送HTTP请求到Solr的admin/cores接口来获取所有core的信息
- 构造Payload:攻击者需要构造一个包含恶意脚本的DataImportHandler配置(dataConfig)。这个配置可以通过HTTP POST请求发送到Solr的/dataimport接口。
- 执行恶意命令:在dataConfig中,攻击者可以定义一个脚本函数,该函数在处理数据时被调用。
- 发送攻击请求:使用工具如BurpSuite发送构造的攻击载荷到Solr服务器。如果服务器存在漏洞,攻击者的命令将被执行。
漏洞复现
首先在页面左侧选择demo核心,打开Dataimport面板,开启右侧Debug mode,填入以下POC:
1 | <dataConfig> |

点击Execute with this Confuguration会发送以下请求包:
1 | POST /solr/demo/dataimport?_=1708782956647&indent=on&wt=json HTTP/1.1 |
执行docker compose exec solr ls /tmp进入容器,可见touch /tmp/success已成功执行:

参考
https://xz.aliyun.com/t/5965?time__1311=n4%2BxnivODQeqBdbDyDmxmq0KDtG8D2YDIObD