java "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=9000" -Dsolr.solr.home="../example/example-DIH/solr/" -jar start.jar --module=http
然后idea加远程调试9000端口,dist、server/lib文件夹加到library里。dist是solr的主要jar
首先看exp
solr的admin后台文件是server/solr-webapp, 所以首先还是得看web.xml,看大概的一些filter、url-pattern路由才能知道后台怎么过去的。
请求都走了SolrRequestFilter这个过滤器,对应着org.apache.solr.servlet.SolrDispatchFilter,进去看一下 看了doFilter方法,主要就是通过getHttpSolrCall返回一个HttpSolrCall对象,然后调用call方法到了servlet类中,然后handler.handleRequest(req, rsp);进到了handleRequest中对请求响应进行处理。
在handleRequest中,this.handleRequestBody(req, rsp);调用了抽象方法来对请求处理,看一下哪些类实现了这个抽象方法。
有点多,这些都是可以通过路由直接请求到的类。这里不仔细看了。
exp是请求/solr/tika/dataimport,所以直接进入exp中的dataimport类。
一开始通过loadDataConfig,其中调用了readFromXml方法,从xml数据中读取信息并解析,根据解析出的document,script,function,dataSource等标签构建出DIHConfiguration对象并返回。然后根据传的参数,没debug,进了这里。 里边只是在新线程中进了runCmd中,还是进了runCmd。根据我们传的full-import,进了这里。在doFullImport中,首先会创建一个DocBuilder对象,DocBuilder的主要功能是从给定配置中创建Solr文档,同时会记录一些状态信息。随后通过execute()方法会通过遍历Entity的所有元素来解析config结构,最终得到是一个EntityProcessorWrapper对象。
下面是通过docBuilder.execute()进入doFullDump然后进入buildDocument方法。可以看到,execute中传入的生成的builder对象的config属性是之前根据我们传入的payload生成的DIHConfiguration对象。buildDocument()方法会为发送的配置数据的每一个processor做解析,当发送的entity中含有Transformers转换器时,会进行相应的转换操作。问题出在ScriptTransformer。script转换器能够对把其他java支持的语言转成java函数执行。而内容我们可控,这就很尴尬了。 引用A-Team大佬的描述:
脚本转换器允许使用Java支持的语言(比如Javascript, JRuby, Jython, Groovy, or BeanShell)编写任意转换函数,其中Javascript语言已默认集成在Java中了,使用其他语言的话需要自己整合。每个转换函数都 必须接收一个row变量(与Java中的Map<String,Object>类型对应,所以是支持get、put、remove等操作的),函数的功能是修改已知field的值,或者添加新的fields。处理完之后,将row对象作为返回值返回。这个脚本会以最高级别插入到DIH配置文件中,每个row会调用一次。
跟buildDocument(), 下面通过epw.nextRow();进入到了applyTransformer,这里就是问题触发点。
首先是loadTransformers,进入加载转换器。 下边startwith判断,如果转换器以script开头,则把ScriptTransformer加到transformers里。
加载转换器后,通过transformers.iterator();遍历所有转换器。跟进t.transformRow,然后就很清晰了。通过initEngine初始化转换引擎,进去看一下,通过eval完成了js函数定义。出来后通过invokeFunction完成了rce 最后说一下,payload中document这部分
这里就是buildDocument中,需要对传入的实体进行解析,根据我们传入的datasource类型加载对应的datasource。 这里主要的作用还是构建我们的script转换器,并且让exp不报错。 如果传一个不存在的datasource会报错无法执行exp。 solr一共有6中datasource,这里用URLDatasource主要是很方便。 网上除了看到URLDataSource外还看到了JdbsDataSource的payload,可以直接ldap+jndi
两次请求,第一个是覆盖了config然后才能velocity模板注入。这里确实牛逼,膜
首先第一个包,他是config进来的。和之前一样,看下configHandler的handleRequestBod
进到command.handlePOST里,继续跟handleCommands, 里边SolrResourceLoader.persistConfLocally,进去看一下
可以看到,直接把我们要覆盖的配置写入configoverlay.json中。这里第一次请求需要做的事情就完成了。
下面看第二次请求。
第二次理论上应该是把configoverlay.json中的配置读出来,然后写到VelocityResponceWriter的配置中,但我跟了几遍代码,都没有找到读文件配置的部分。。配置是true和false的区别就在初始化velocity对象时,对象的配置值不同。。这里应该是内存中的值,当文件中的值更改后,会进入init方法中重置该值(大概)。 继续往下跟,进入到在createInitInstance中,通过解析请求参数中的class,使createInstance方法创建一个VelocityResponceWriter对象,然后调用他的init方法。 跟进能看到prle和srle就是传入的要覆盖的属性。他们被覆盖到了VelocityResponceWriter对象的this.paramsResourceLoaderEnabled和this.solrResourceLoaderEnabled属性。 当到了createEngine的时候可以看到,paramsResourceLoaderEnabled是true时将params加到了loader里。接下来通过engine.getTemplate(templateName + ".vm");就能够从url中获得传过去的exp。
然后返回继续到template.merge进入velocity中,然后继续跟一下会发现在render中通过ASTprocess解析注入的模板完成RCE 而当是false时,engine.getTemplate会报错找不到该vm,原因就是paramsResourceLoaderEnabled为false时,velocity并不会从url中获得velocity的模板字符串,从而无法进行模板注入。 可以继续跟一下Solr的各个handler,感觉可能还会有其他直接render的地方能直接ssti。
本文作者:PIG-Z