From 1308bc30c8c995ffeae4294d6b1806254320e209 Mon Sep 17 00:00:00 2001 From: mpgn Date: Sun, 3 May 2020 14:30:41 -0400 Subject: [PATCH] Adding Kerberos support for CME #22 TODO - aeskey - dc-ip - checkifadmin() --- cme/cli.py | 1 + cme/connection.py | 189 ++++++++++++++++++----------------- cme/helpers/misc.py | 2 +- cme/protocols/smb.py | 21 +++- cme/protocols/smb/atexec.py | 12 ++- cme/protocols/smb/smbexec.py | 14 ++- cme/protocols/smb/wmiexec.py | 7 +- 7 files changed, 140 insertions(+), 106 deletions(-) diff --git a/cme/cli.py b/cme/cli.py index e85dda0c..a75d6100 100755 --- a/cme/cli.py +++ b/cme/cli.py @@ -47,6 +47,7 @@ def gen_cli_args(): std_parser.add_argument('-id', metavar="CRED_ID", nargs='+', default=[], type=str, dest='cred_id', help='database credential ID(s) to use for authentication') std_parser.add_argument("-u", metavar="USERNAME", dest='username', nargs='+', default=[], help="username(s) or file(s) containing usernames") std_parser.add_argument("-p", metavar="PASSWORD", dest='password', nargs='+', default=[], help="password(s) or file(s) containing passwords") + std_parser.add_argument("-k", "--kerberos", action='store_true', help="Use Kerberos authentication from ccache file (KRB5CCNAME)") fail_group = std_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') diff --git a/cme/connection.py b/cme/connection.py index ed331f49..88641a0e 100755 --- a/cme/connection.py +++ b/cme/connection.py @@ -28,13 +28,18 @@ class connection(object): self.conn = None self.admin_privs = False self.logger = None - self.password = None - self.username = None + self.password = '' + self.username = '' + self.kerberos = True if self.args.kerberos else False + self.aesKey = None + self.dc_ip = None self.failed_logins = 0 self.local_ip = None try: self.host = gethostbyname(self.hostname) + if self.args.kerberos: + self.host = self.hostname except Exception as e: logging.debug('Error resolving hostname {}: {}'.format(self.hostname, e)) return @@ -60,6 +65,9 @@ class connection(object): def check_if_admin(self): return + def kerberos_login(self): + return + def plaintext_login(self, domain, username, password): return @@ -133,110 +141,113 @@ class connection(object): return False def login(self): - for cred_id in self.args.cred_id: - with sem: - if cred_id.lower() == 'all': - creds = self.db.get_credentials() - else: - creds = self.db.get_credentials(filterTerm=int(cred_id)) + if self.args.kerberos: + if self.kerberos_login(): return True + else: + for cred_id in self.args.cred_id: + with sem: + if cred_id.lower() == 'all': + creds = self.db.get_credentials() + else: + creds = self.db.get_credentials(filterTerm=int(cred_id)) - for cred in creds: - logging.debug(cred) - try: - c_id, domain, username, password, credtype, pillaged_from = cred + for cred in creds: + logging.debug(cred) + try: + c_id, domain, username, password, credtype, pillaged_from = cred - if credtype and password: + if credtype and password: - if not domain: domain = self.domain + if not domain: domain = self.domain - if self.args.local_auth: - domain = self.domain - elif self.args.domain: - domain = self.args.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 True + if credtype == 'hash' and not self.over_fail_limit(username): + if self.hash_login(domain, username, password): return True - elif credtype == 'plaintext' and not self.over_fail_limit(username): - if self.plaintext_login(domain, username, password): return True + elif credtype == 'plaintext' and not self.over_fail_limit(username): + if self.plaintext_login(domain, username, password): return True - except IndexError: - self.logger.error("Invalid database credential ID!") + except IndexError: + self.logger.error("Invalid database credential ID!") - for user in self.args.username: - if not isinstance(user, str) and isfile(user.name): - for usr in user: - if "\\" in usr: - tmp = usr - usr = tmp.split('\\')[1].strip() - self.domain = tmp.split('\\')[0] - if self.args.hash: - with sem: - for ntlm_hash in self.args.hash: - if isinstance(ntlm_hash, str): - if not self.over_fail_limit(usr.strip()): - if self.hash_login(self.domain, usr.strip(), ntlm_hash): return True - - elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == False: - for f_hash in ntlm_hash: + for user in self.args.username: + if not isinstance(user, str) and isfile(user.name): + for usr in user: + if "\\" in usr: + tmp = usr + usr = tmp.split('\\')[1].strip() + self.domain = tmp.split('\\')[0] + if self.args.hash: + with sem: + for ntlm_hash in self.args.hash: + if isinstance(ntlm_hash, str): if not self.over_fail_limit(usr.strip()): - if self.hash_login(self.domain, usr.strip(), f_hash.strip()): return True - ntlm_hash.seek(0) + if self.hash_login(self.domain, usr.strip(), ntlm_hash): return True - elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == True: - user.seek(0) - for usr, f_pass in zip(user, ntlm_hash): + elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == False: + 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 True + ntlm_hash.seek(0) + + elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name) and self.args.no_bruteforce == True: + user.seek(0) + for usr, f_pass in zip(user, ntlm_hash): + if not self.over_fail_limit(usr.strip()): + if self.plaintext_login(self.domain, usr.strip(), f_hash.strip()): return True + + elif self.args.password: + with sem: + for password in self.args.password: + if isinstance(password, str): if not self.over_fail_limit(usr.strip()): - if self.plaintext_login(self.domain, usr.strip(), f_hash.strip()): return True + if self.plaintext_login(self.domain, usr.strip(), password): return True - elif self.args.password: - with sem: - for password in self.args.password: - if isinstance(password, str): - if not self.over_fail_limit(usr.strip()): - if self.plaintext_login(self.domain, usr.strip(), password): return True + elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == False: + 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 True + password.seek(0) - elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == False: - 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 True - password.seek(0) + elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == True: + user.seek(0) + for usr, f_pass in zip(user, password): + if not self.over_fail_limit(usr.strip()): + if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True - elif not isinstance(password, str) and isfile(password.name) and self.args.no_bruteforce == True: - user.seek(0) - for usr, f_pass in zip(user, password): - if not self.over_fail_limit(usr.strip()): - if self.plaintext_login(self.domain, usr.strip(), f_pass.strip()): return True - - elif isinstance(user, str): - if hasattr(self.args, 'hash') and self.args.hash: - with sem: - for ntlm_hash in self.args.hash: - if isinstance(ntlm_hash, str): - if not self.over_fail_limit(user): - if self.hash_login(self.domain, user, ntlm_hash): return True - - elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name): - for f_hash in ntlm_hash: + elif isinstance(user, str): + if hasattr(self.args, 'hash') and self.args.hash: + with sem: + for ntlm_hash in self.args.hash: + if isinstance(ntlm_hash, str): if not self.over_fail_limit(user): - if self.hash_login(self.domain, user, f_hash.strip()): return True - ntlm_hash.seek(0) + if self.hash_login(self.domain, user, ntlm_hash): return True - elif self.args.password: - with sem: - for password in self.args.password: - if isinstance(password, str): - if not self.over_fail_limit(user): - if hasattr(self.args, 'domain'): - if self.plaintext_login(self.domain, user, password): return True - else: - if self.plaintext_login(user, password): return True + elif not isinstance(ntlm_hash, str) and isfile(ntlm_hash.name): + for f_hash in ntlm_hash: + if not self.over_fail_limit(user): + if self.hash_login(self.domain, user, f_hash.strip()): return True + ntlm_hash.seek(0) - elif not isinstance(password, str) and isfile(password.name): - for f_pass in password: + elif self.args.password: + with sem: + for password in self.args.password: + if isinstance(password, str): if not self.over_fail_limit(user): if hasattr(self.args, 'domain'): - if self.plaintext_login(self.domain, user, f_pass.strip()): return True + if self.plaintext_login(self.domain, user, password): return True else: - if self.plaintext_login(user, f_pass.strip()): return True - password.seek(0) + if self.plaintext_login(user, password): return True + + elif not isinstance(password, str) and isfile(password.name): + for f_pass in password: + if not self.over_fail_limit(user): + if hasattr(self.args, 'domain'): + if self.plaintext_login(self.domain, user, f_pass.strip()): return True + else: + if self.plaintext_login(user, f_pass.strip()): return True + password.seek(0) diff --git a/cme/helpers/misc.py b/cme/helpers/misc.py index ca21f0b6..0fa146db 100755 --- a/cme/helpers/misc.py +++ b/cme/helpers/misc.py @@ -33,7 +33,7 @@ def called_from_cmd_args(): for stack in inspect.stack(): if stack[3] == 'print_host_info': return True - if stack[3] == 'plaintext_login' or stack[3] == 'hash_login': + if stack[3] == 'plaintext_login' or stack[3] == 'hash_login' or stack[3] == 'kerberos_login': return True if stack[3] == 'call_cmd_args': return True diff --git a/cme/protocols/smb.py b/cme/protocols/smb.py index 2c359c4d..0a6a4b8c 100755 --- a/cme/protocols/smb.py +++ b/cme/protocols/smb.py @@ -11,6 +11,7 @@ from impacket.nmb import NetBIOSError from impacket.dcerpc.v5 import transport, lsat, lsad from impacket.dcerpc.v5.rpcrt import DCERPCException from impacket.dcerpc.v5.transport import DCERPCTransportFactory +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE from impacket.dcerpc.v5.epm import MSRPC_UUID_PORTMAP from impacket.dcerpc.v5.dcom.wmi import WBEM_FLAG_FORWARD_ONLY from impacket.dcerpc.v5.samr import SID_NAME_USE @@ -185,6 +186,8 @@ class smb(connection): transport = DCERPCTransportFactory(stringBinding) transport.set_connect_timeout(5) dce = transport.get_dce_rpc() + if self._conn.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')) @@ -221,6 +224,9 @@ class smb(connection): if not self.domain: self.domain = self.hostname + if self.args.kerberos: + self.domain = self.conn.getServerDNSDomainName() + self.db.add_computer(self.host, self.hostname, self.domain, self.server_os) try: @@ -248,6 +254,15 @@ class smb(connection): self.domain, self.signing, self.smbv1)) + def kerberos_login(self): + self.conn.kerberosLogin('', '', self.domain, self.lmhash, self.nthash, self.aesKey, self.dc_ip) + # self.check_if_admin() # currently not working with kerberos so we set admin_privs to True + self.admin_privs = True + 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 def plaintext_login(self, domain, username, password): try: @@ -395,7 +410,7 @@ class smb(connection): if method == 'wmiexec': try: - exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.hash, self.args.share) + exec_method = WMIEXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.conn, self.kerberos, self.hash, self.args.share) logging.debug('Executed command via wmiexec') break except: @@ -415,7 +430,7 @@ class smb(connection): elif method == 'atexec': try: - exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.hash) #self.args.share) + exec_method = TSCH_EXEC(self.host, self.smb_share_name, self.username, self.password, self.domain, self.kerberos, self.hash) #self.args.share) logging.debug('Executed command via atexec') break except: @@ -425,7 +440,7 @@ class smb(connection): elif method == 'smbexec': try: - exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.port, self.username, self.password, self.domain, self.hash, self.args.share) + exec_method = SMBEXEC(self.host, self.smb_share_name, self.args.port, self.username, self.password, self.domain, self.kerberos, self.hash, self.args.share) logging.debug('Executed command via smbexec') break except: diff --git a/cme/protocols/smb/atexec.py b/cme/protocols/smb/atexec.py index 2453b09a..a82f4ef3 100755 --- a/cme/protocols/smb/atexec.py +++ b/cme/protocols/smb/atexec.py @@ -2,11 +2,12 @@ import os import logging from impacket.dcerpc.v5 import tsch, transport from impacket.dcerpc.v5.dtypes import NULL +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE from cme.helpers.misc import gen_random_string from gevent import sleep class TSCH_EXEC: - def __init__(self, target, share_name, username, password, domain, hashes=None): + def __init__(self, target, share_name, username, password, domain, doKerberos=False, hashes=None): self.__target = target self.__username = username self.__password = password @@ -16,8 +17,9 @@ class TSCH_EXEC: self.__nthash = '' self.__outputBuffer = b'' self.__retOutput = False - #self.__aesKey = aesKey - #self.__doKerberos = doKerberos + # self.__aesKey = aesKey + self.__doKerberos = doKerberos + self.__kdcHost = None if hashes is not None: #This checks to see if we didn't provide the LM Hash @@ -35,7 +37,7 @@ class TSCH_EXEC: if hasattr(self.__rpctransport, 'set_credentials'): # This method exists only for selected protocol sequences. self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) - #rpctransport.set_kerberos(self.__doKerberos) + self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) def execute(self, command, output=False): self.__retOutput = output @@ -124,6 +126,8 @@ class TSCH_EXEC: def doStuff(self, command, fileless=False): dce = self.__rpctransport.get_dce_rpc() + if self.__doKerberos: + dce.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) dce.set_credentials(*self.__rpctransport.get_credentials()) dce.connect() diff --git a/cme/protocols/smb/smbexec.py b/cme/protocols/smb/smbexec.py index 14232192..8215a83c 100755 --- a/cme/protocols/smb/smbexec.py +++ b/cme/protocols/smb/smbexec.py @@ -4,10 +4,11 @@ from gevent import sleep from impacket.dcerpc.v5 import transport, scmr from impacket.smbconnection import * from cme.helpers.misc import gen_random_string +from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_GSS_NEGOTIATE class SMBEXEC: - def __init__(self, host, share_name, protocol, username = '', password = '', domain = '', hashes = None, share = None, port=445): + def __init__(self, host, share_name, protocol, username = '', password = '', domain = '', doKerberos=False, hashes = None, share = None, port=445): self.__host = host self.__share_name = share_name self.__port = port @@ -26,9 +27,10 @@ class SMBEXEC: self.__rpctransport = None self.__scmr = None self.__conn = None - #self.__mode = mode - #self.__aesKey = aesKey - #self.__doKerberos = doKerberos + # self.__mode = mode + # self.__aesKey = aesKey + self.__doKerberos = doKerberos + self.__kdcHost = None if hashes is not None: #This checks to see if we didn't provide the LM Hash @@ -50,9 +52,11 @@ class SMBEXEC: if hasattr(self.__rpctransport, 'set_credentials'): # This method exists only for selected protocol sequences. self.__rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) - #rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) + self.__rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) self.__scmr = self.__rpctransport.get_dce_rpc() + if self.__doKerberos: + self.__scmr.set_auth_type(RPC_C_AUTHN_GSS_NEGOTIATE) self.__scmr.connect() s = self.__rpctransport.get_smb_connection() # We don't wanna deal with timeouts from now on. diff --git a/cme/protocols/smb/wmiexec.py b/cme/protocols/smb/wmiexec.py index 93f184c3..5a3d635d 100755 --- a/cme/protocols/smb/wmiexec.py +++ b/cme/protocols/smb/wmiexec.py @@ -8,7 +8,7 @@ from impacket.dcerpc.v5.dcom import wmi from impacket.dcerpc.v5.dtypes import NULL class WMIEXEC: - def __init__(self, target, share_name, username, password, domain, smbconnection, hashes=None, share=None): + def __init__(self, target, share_name, username, password, domain, smbconnection, doKerberos=False, hashes=None, share=None): self.__target = target self.__username = username self.__password = password @@ -23,7 +23,7 @@ class WMIEXEC: self.__shell = 'cmd.exe /Q /c ' self.__pwd = 'C:\\' self.__aesKey = None - self.__doKerberos = False + self.__doKerberos = doKerberos self.__retOutput = True if hashes is not None: @@ -35,8 +35,7 @@ class WMIEXEC: if self.__password is None: self.__password = '' - - self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver = True, doKerberos=self.__doKerberos) + self.__dcom = DCOMConnection(self.__target, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos) iInterface = self.__dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login) iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)