From d63e4fb59f870ee8cdc1f949637d8c5b28ea6d7d Mon Sep 17 00:00:00 2001 From: p0dalirius Date: Wed, 14 Dec 2022 13:34:18 +0100 Subject: [PATCH] Fixed #7 --- FindUncommonShares.py | 290 +++--------------------------------------- requirements.txt | 1 - 2 files changed, 16 insertions(+), 275 deletions(-) diff --git a/FindUncommonShares.py b/FindUncommonShares.py index e74282e..4a883a1 100755 --- a/FindUncommonShares.py +++ b/FindUncommonShares.py @@ -10,6 +10,8 @@ from impacket import version from impacket.smbconnection import SMBConnection, SMB2_DIALECT_002, SMB2_DIALECT_21, SMB_DIALECT, SessionError from impacket.spnego import SPNEGO_NegTokenInit, TypesMech +from sectools.windows.ldap import raw_ldap_query +from sectools.windows.crypto import nt_hash, parse_lm_nt_hashes import argparse import binascii import dns.resolver, dns.exception @@ -78,49 +80,6 @@ def STYPE_MASK(stype_value): return flags -def get_domain_computers(ldap_server, ldap_session): - page_size = 1000 - # Controls - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/3c5e87db-4728-4f29-b164-01dd7d7391ea - LDAP_PAGED_RESULT_OID_STRING = "1.2.840.113556.1.4.319" - # https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/f14f3610-ee22-4d07-8a24-1bf1466cba5f - LDAP_SERVER_NOTIFICATION_OID = "1.2.840.113556.1.4.528" - results = {} - - target_dn = ldap_server.info.other["defaultNamingContext"] - - # https://ldap3.readthedocs.io/en/latest/searches.html#the-search-operation - paged_response = True - paged_cookie = None - while paged_response == True: - ldap_session.search( - target_dn, "(objectCategory=computer)", attributes=["dNSHostName", "sAMAccountName"], - size_limit=0, paged_size=page_size, paged_cookie=paged_cookie - ) - # - if "controls" in ldap_session.result.keys(): - if LDAP_PAGED_RESULT_OID_STRING in ldap_session.result["controls"].keys(): - next_cookie = ldap_session.result["controls"][LDAP_PAGED_RESULT_OID_STRING]["value"]["cookie"] - if len(next_cookie) == 0: - paged_response = False - else: - paged_response = True - paged_cookie = next_cookie - else: - paged_response = False - else: - paged_response = False - # - for entry in ldap_session.response: - if entry['type'] != 'searchResEntry': - continue - results[entry['dn']] = { - 'dNSHostName': entry["attributes"]['dNSHostName'], - 'sAMAccountName': entry["attributes"]['sAMAccountName'] - } - return results - - def parse_args(): print("FindUncommonShares v%s - by @podalirius_\n" % VERSION) @@ -178,215 +137,6 @@ def get_machine_name(options, domain): return s.getServerName() -def init_ldap_connection(target, tls_version, options, domain, username, password, lmhash, nthash): - user = '%s\\%s' % (domain, username) - if tls_version is not None: - use_ssl = True - port = 636 - tls = ldap3.Tls(validate=ssl.CERT_NONE, version=tls_version) - else: - use_ssl = False - port = 389 - tls = None - ldap_server = ldap3.Server(target, get_info=ldap3.ALL, port=port, use_ssl=use_ssl, tls=tls) - - if options.use_kerberos: - ldap_session = ldap3.Connection(ldap_server) - ldap_session.bind() - ldap3_kerberos_login(ldap_session, target, username, password, domain, lmhash, nthash, options.auth_key, kdcHost=options.dc_ip) - elif options.auth_hashes is not None: - if lmhash == "": - lmhash = "aad3b435b51404eeaad3b435b51404ee" - ldap_session = ldap3.Connection(ldap_server, user=user, password=lmhash + ":" + nthash, authentication=ldap3.NTLM, auto_bind=True) - else: - ldap_session = ldap3.Connection(ldap_server, user=user, password=password, authentication=ldap3.NTLM, auto_bind=True) - - return ldap_server, ldap_session - - -def init_ldap_session(options, domain, username, password, lmhash, nthash): - if options.use_kerberos: - target = get_machine_name(options, domain) - else: - if options.dc_ip is not None: - target = options.dc_ip - else: - target = domain - - if options.use_ldaps is True: - try: - return init_ldap_connection(target, ssl.PROTOCOL_TLSv1_2, options, domain, username, password, lmhash, nthash) - except ldap3.core.exceptions.LDAPSocketOpenError: - return init_ldap_connection(target, ssl.PROTOCOL_TLSv1, options, domain, username, password, lmhash, nthash) - else: - return init_ldap_connection(target, None, options, domain, username, password, lmhash, nthash) - - -def ldap3_kerberos_login(connection, target, user, password, domain='', lmhash='', nthash='', aesKey='', kdcHost=None, TGT=None, TGS=None, useCache=True, debug=False): - from pyasn1.codec.ber import encoder, decoder - from pyasn1.type.univ import noValue - """ - logins into the target system explicitly using Kerberos. Hashes are used if RC4_HMAC is supported. - :param string user: username - :param string password: password for the user - :param string domain: domain where the account is valid for (required) - :param string lmhash: LMHASH used to authenticate using hashes (password is not used) - :param string nthash: NTHASH used to authenticate using hashes (password is not used) - :param string aesKey: aes256-cts-hmac-sha1-96 or aes128-cts-hmac-sha1-96 used for Kerberos authentication - :param string kdcHost: hostname or IP Address for the KDC. If None, the domain will be used (it needs to resolve tho) - :param struct TGT: If there's a TGT available, send the structure here and it will be used - :param struct TGS: same for TGS. See smb3.py for the format - :param bool useCache: whether or not we should use the ccache for credentials lookup. If TGT or TGS are specified this is False - :return: True, raises an Exception if error. - """ - - if lmhash != '' or nthash != '': - if len(lmhash) % 2: - lmhash = '0' + lmhash - if len(nthash) % 2: - nthash = '0' + nthash - try: # just in case they were converted already - lmhash = binascii.unhexlify(lmhash) - nthash = binascii.unhexlify(nthash) - except TypeError: - pass - - # Importing down here so pyasn1 is not required if kerberos is not used. - from impacket.krb5.ccache import CCache - from impacket.krb5.asn1 import AP_REQ, Authenticator, TGS_REP, seq_set - from impacket.krb5.kerberosv5 import getKerberosTGT, getKerberosTGS - from impacket.krb5 import constants - from impacket.krb5.types import Principal, KerberosTime, Ticket - import datetime - - if TGT is not None or TGS is not None: - useCache = False - - if useCache: - try: - ccache = CCache.loadFile(os.getenv('KRB5CCNAME')) - except Exception as e: - # No cache present - print(e) - pass - else: - # retrieve domain information from CCache file if needed - if domain == '': - domain = ccache.principal.realm['data'].decode('utf-8') - if debug: - print('[debug] Domain retrieved from CCache: %s' % domain) - - if debug: - print('[debug] Using Kerberos Cache: %s' % os.getenv('KRB5CCNAME')) - principal = 'ldap/%s@%s' % (target.upper(), domain.upper()) - - creds = ccache.getCredential(principal) - if creds is None: - # Let's try for the TGT and go from there - principal = 'krbtgt/%s@%s' % (domain.upper(), domain.upper()) - creds = ccache.getCredential(principal) - if creds is not None: - TGT = creds.toTGT() - if debug: - print('[debug] Using TGT from cache') - else: - if debug: - print('[debug] No valid credentials found in cache') - else: - TGS = creds.toTGS(principal) - if debug: - print('[debug] Using TGS from cache') - - # retrieve user information from CCache file if needed - if user == '' and creds is not None: - user = creds['client'].prettyPrint().split(b'@')[0].decode('utf-8') - if debug: - print('[debug] Username retrieved from CCache: %s' % user) - elif user == '' and len(ccache.principal.components) > 0: - user = ccache.principal.components[0]['data'].decode('utf-8') - if debug: - print('[debug] Username retrieved from CCache: %s' % user) - - # First of all, we need to get a TGT for the user - userName = Principal(user, type=constants.PrincipalNameType.NT_PRINCIPAL.value) - if TGT is None: - if TGS is None: - tgt, cipher, oldSessionKey, sessionKey = getKerberosTGT(userName, password, domain, lmhash, nthash, aesKey, kdcHost) - else: - tgt = TGT['KDC_REP'] - cipher = TGT['cipher'] - sessionKey = TGT['sessionKey'] - - if TGS is None: - serverName = Principal('ldap/%s' % target, type=constants.PrincipalNameType.NT_SRV_INST.value) - tgs, cipher, oldSessionKey, sessionKey = getKerberosTGS(serverName, domain, kdcHost, tgt, cipher, sessionKey) - else: - tgs = TGS['KDC_REP'] - cipher = TGS['cipher'] - sessionKey = TGS['sessionKey'] - - # Let's build a NegTokenInit with a Kerberos REQ_AP - - blob = SPNEGO_NegTokenInit() - - # Kerberos - blob['MechTypes'] = [TypesMech['MS KRB5 - Microsoft Kerberos 5']] - - # Let's extract the ticket from the TGS - tgs = decoder.decode(tgs, asn1Spec=TGS_REP())[0] - ticket = Ticket() - ticket.from_asn1(tgs['ticket']) - - # Now let's build the AP_REQ - apReq = AP_REQ() - apReq['pvno'] = 5 - apReq['msg-type'] = int(constants.ApplicationTagNumbers.AP_REQ.value) - - opts = [] - apReq['ap-options'] = constants.encodeFlags(opts) - seq_set(apReq, 'ticket', ticket.to_asn1) - - authenticator = Authenticator() - authenticator['authenticator-vno'] = 5 - authenticator['crealm'] = domain - seq_set(authenticator, 'cname', userName.components_to_asn1) - now = datetime.datetime.utcnow() - - authenticator['cusec'] = now.microsecond - authenticator['ctime'] = KerberosTime.to_asn1(now) - - encodedAuthenticator = encoder.encode(authenticator) - - # Key Usage 11 - # AP-REQ Authenticator (includes application authenticator - # subkey), encrypted with the application session key - # (Section 5.5.1) - encryptedEncodedAuthenticator = cipher.encrypt(sessionKey, 11, encodedAuthenticator, None) - - apReq['authenticator'] = noValue - apReq['authenticator']['etype'] = cipher.enctype - apReq['authenticator']['cipher'] = encryptedEncodedAuthenticator - - blob['MechToken'] = encoder.encode(apReq) - - request = ldap3.operation.bind.bind_operation(connection.version, ldap3.SASL, user, None, 'GSS-SPNEGO', - blob.getData()) - - # Done with the Kerberos saga, now let's get into LDAP - if connection.closed: # try to open connection if closed - connection.open(read_server_info=False) - - connection.sasl_in_progress = True - response = connection.post_send_single_response(connection.send('bindRequest', request, None)) - connection.sasl_in_progress = False - if response[0]['result'] != 0: - raise Exception(response) - - connection.bound = True - - return True - - def init_smb_session(options, target_ip, domain, username, password, address, lmhash, nthash, port=445, debug=False): smbClient = SMBConnection(address, target_ip, sess_port=int(port)) dialect = smbClient.getDialect() @@ -421,7 +171,7 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt dns_answer = None # Try UDP try: - dns_answer = dns_resolver.resolve(domain, rdtype=record_type, tcp=False) + dns_answer = dns_resolver.resolve(target_name, rdtype="A", tcp=False) except dns.resolver.NXDOMAIN: # the domain does not exist so dns resolutions remain empty pass @@ -436,7 +186,7 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt if dns_answer is None: # Try TCP try: - dns_answer = dns_resolver.resolve(domain, rdtype=record_type, tcp=True) + dns_answer = dns_resolver.resolve(target_name, rdtype="A", tcp=True) except dns.resolver.NXDOMAIN: # the domain does not exist so dns resolutions remain empty pass @@ -450,7 +200,7 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt target_ip = [] if dns_answer is not None: - target_ip = dns_answer.answer + target_ip = [ip.address for ip in dns_answer] if len(target_ip) != 0: target_ip = target_ip[0] @@ -540,28 +290,19 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt if __name__ == '__main__': options = parse_args() - - auth_lm_hash = "" - auth_nt_hash = "" - if options.auth_hashes is not None: - if ":" in options.auth_hashes: - auth_lm_hash = options.auth_hashes.split(":")[0] - auth_nt_hash = options.auth_hashes.split(":")[1] - else: - auth_nt_hash = options.auth_hashes - - ldap_server, ldap_session = init_ldap_session( - options=options, - domain=options.auth_domain, - username=options.auth_username, - password=options.auth_password, - lmhash=auth_lm_hash, - nthash=auth_nt_hash - ) + auth_lm_hash, auth_nt_hash = parse_lm_nt_hashes(options.auth_hashes) if not options.quiet: print("[>] Extracting all computers ...") - computers = get_domain_computers(ldap_server, ldap_session) + computers = raw_ldap_query( + auth_domain=options.auth_domain, + auth_dc_ip=options.dc_ip, + auth_username=options.auth_username, + auth_password=options.auth_password, + auth_hashes=options.auth_hashes, + query='(objectCategory=computer)', + attributes=["dNSHostName", "sAMAccountName"] + ) if not options.quiet: print("[+] Found %d computers in the domain. \n" % len(computers.keys())) @@ -653,4 +394,5 @@ def worker(options, target_name, domain, username, password, address, lmhash, nt conn.commit() conn.close() print("done.") + print("[+] Bye Bye!") diff --git a/requirements.txt b/requirements.txt index 21d8cb7..15d3554 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ impacket -nslookup xlsxwriter \ No newline at end of file