From fbeb4541731a8e4600443d87fc7c0059c9e8020a Mon Sep 17 00:00:00 2001 From: zblurx Date: Thu, 11 Jul 2024 14:38:28 +0200 Subject: [PATCH] kerberos fix --- donpapi/collectors/sccm.py | 2 +- donpapi/core.py | 19 +++++------ donpapi/entry.py | 70 ++++++++++++++++++++++++++------------ donpapi/lib/utils.py | 5 ++- pyproject.toml | 4 +-- 5 files changed, 65 insertions(+), 35 deletions(-) diff --git a/donpapi/collectors/sccm.py b/donpapi/collectors/sccm.py index bef1921..88ce68e 100644 --- a/donpapi/collectors/sccm.py +++ b/donpapi/collectors/sccm.py @@ -21,7 +21,7 @@ class SCCMDump: def run(self): if self.context.remoteops_allowed: self.logger.display("Dumping SCCM Credentials") - for wmi in [False,True]: + for wmi in [True,False]: sccm_triage = SCCMTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys, use_wmi=wmi) sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm() for sccmcred in sccmcreds: diff --git a/donpapi/core.py b/donpapi/core.py index e9dd1f9..8c1bd7d 100644 --- a/donpapi/core.py +++ b/donpapi/core.py @@ -42,20 +42,18 @@ class DonPAPICore: self.lsa_dump = None self.dpapi_systemkey = None self.hostname = None - self.dploot_target = Target.create( domain=options.domain, - username=options.username, - password=options.password, + username=options.username if options.username is not None else "", + password=options.password if options.password is not None else "", target=self.host, - lmhash=options.lmhash, - nthash=options.nthash, - do_kerberos=options.k, - no_pass=options.no_pass, + lmhash=options.lmhash if options.lmhash is not None else "", + nthash=options.nthash if options.nthash is not None else "", + do_kerberos=options.k or options.aesKey, + no_pass=True, aesKey=options.aesKey, - use_kcache=options.aesKey or options.k, + use_kcache=options.k, ) - self.logger = DonPAPIAdapter() if not self.init_connection(): return @@ -258,7 +256,7 @@ class DonPAPICore: masterkeys += masterkeys_triage.triage_system_masterkeys() except Exception as e: self.logger.debug(f"Could not get masterkeys: {e}") - self.masterkeys = masterkeys + self.masterkeys += masterkeys def run(self): self.logger.display("Starting gathering credz") @@ -305,6 +303,7 @@ class DonPAPICore: self.logger.fail(f"Error during {collector.__name__}: {e}") # Get yadayadayada + self.logger.verbose("Finished thread") @property def is_admin(self) -> bool: diff --git a/donpapi/entry.py b/donpapi/entry.py index f59c686..91fd904 100644 --- a/donpapi/entry.py +++ b/donpapi/entry.py @@ -88,25 +88,49 @@ def fetch_all_computers(options): donpapi_logger.display(f"Collecting every hostnames from {options.domain}") results = None hostnames = [] + ldap_filter = "(objectCategory=computer)" + attributes = [ + "name", + ] + + dc_hostname = None + base_dn = None + try: - base_dn = 'dc=' - base_dn += ",dc=".join(options.domain.split('.')) - ldap_filter = "(objectCategory=computer)" - attributes = [ - "name", - ] ldap_url = f"ldap://{options.domain}" - ldap_connection = ldap.LDAPConnection(ldap_url, base_dn) + donpapi_logger.verbose(f"Connecting to {ldap_url} with no baseDN") + ldap_connection = ldap.LDAPConnection(ldap_url, dstIp=options.dc_ip) + resp = ldap_connection.search( + scope=ldapasn1.Scope("baseObject"), + attributes=["defaultNamingContext", "dnsHostName"], + sizeLimit=0, + ) + for item in resp: + if isinstance(item, ldapasn1.SearchResultEntry) is not True: + continue + + for attribute in item["attributes"]: + if str(attribute["type"]) == "defaultNamingContext": + base_dn = str(attribute["vals"][0]) + if str(attribute["type"]) == "dnsHostName": + dc_hostname = str(attribute["vals"][0]) + except Exception as e: + donpapi_logger.error(f"Exception while getting ldap info: {e}") + + try: + ldap_url = f"ldap://{dc_hostname}" + ldap_connection = ldap.LDAPConnection(ldap_url, base_dn, dstIp=options.dc_ip) if options.k or options.aesKey: # Kerberos connection ldap_connection.kerberosLogin( - options.username, - options.password, - options.domain, - options.lmhash, - options.nthash, - options.aesKey, + options.username if options.username is not None else "", + options.password if options.password is not None else "", + options.domain , + options.lmhash if options.lmhash is not None else "", + options.nthash if options.nthash is not None else "", + options.aesKey if options.aesKey is not None else "", useCache=options.k, + kdcHost=options.dc_ip ) else: # NTLM connection @@ -126,7 +150,9 @@ def fetch_all_computers(options): donpapi_logger.error(f"Exception while requesting targets: {e}") import traceback traceback.print_exc() - + if results is None: + donpapi_logger.error("Could not get hostnames from LDAP") + return [] results = [r for r in results if isinstance(r, ldapasn1.SearchResultEntry)] for computer in results: values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]} @@ -148,13 +174,13 @@ def fetch_domain_backupkey(options, db: Database): try: dc_target = Target.create( domain=options.domain, - username=options.username, + username=options.username if options.username is not None else "", password=options.password, target=options.domain if options.domain != "" else options.dc_ip, lmhash=options.lmhash, nthash=options.nthash, do_kerberos=options.k, - no_pass=options.no_pass, + no_pass=True, aesKey=options.aesKey, use_kcache=options.aesKey or options.k, ) @@ -207,7 +233,7 @@ def main(): group_authent.add_argument("-r", "--recover-file", metavar="/home/user/.donpapi/recover/recover_1718281433", type=str, help="The recover file path. If used, the other parameters will be ignored") group_attacks = collect_subparser.add_argument_group('attacks') - group_attacks.add_argument('-c','--collectors', action="store", default="All", help= ", ".join(COLLECTORS_LIST.keys())+", All (all previous) (default: All)") + group_attacks.add_argument('-c','--collectors', action="store", default="All", help= ", ".join(COLLECTORS_LIST.keys())+", All (all previous) (default: All). Possible to chain multiple collectors comma separated") group_attacks.add_argument("-nr","--no-remoteops", action="store_true", help="Disable Remote Ops operations (basically no Remote Registry operations, no DPAPI System Credentials)") group_attacks.add_argument("--fetch-pvk", action="store_true", help=("Will automatically use domain backup key from database, and if not already dumped, will dump it on a domain controller")) group_attacks.add_argument("--pvkfile", action="store", help=("Pvk file with domain backup key")) @@ -323,10 +349,11 @@ def main(): for target in options.target: if target == "ALL": # Target every computers from domain - if hasattr(options, "domain"): + if hasattr(options, "domain") and options.domain != "": targets.extend(fetch_all_computers(options)) else: donpapi_logger.error("--domain required with --target ALL") + return else: if os.path.exists(target) and os.path.isfile(target): with open(target) as target_file: @@ -389,9 +416,10 @@ async def start_dpp(options, db, targets, current_target_recovered, collectors, def create_dpp_thread(options, db, targets, current_target_recovered, collectors, pvkbytes, passwords, nthashes, masterkeys, donpapi_config, output_dir, progress_bar, task, executor): # Recover file progress_bar.update(task, completed=0 if len(current_target_recovered) == 0 else len(targets) - len(current_target_recovered)) + current_targets = copy.deepcopy(targets) if len(current_target_recovered) > 0: # finishing targets - targets = current_target_recovered + current_targets = current_target_recovered recover_filename = create_recover_file(targets=targets, dirpath=output_dir, options=options) donpapi_logger.display(f"Recover file available at {recover_filename}") try: @@ -399,8 +427,8 @@ def create_dpp_thread(options, db, targets, current_target_recovered, collectors future = [executor.submit(core_run,(options, db, target, collectors, pvkbytes, passwords, nthashes, masterkeys, donpapi_config, output_dir)) for target in targets] for i in as_completed(future): target_finished = i.result() - targets.remove(target_finished) - update_recover_file(recover_file_handle, targets) + current_targets.remove(target_finished) + update_recover_file(recover_file_handle, current_targets) progress_bar.update(task, advance=1) except KeyboardInterrupt: donpapi_logger.error("Caugth Keyboard Interrupt. Gracefully shutdown") diff --git a/donpapi/lib/utils.py b/donpapi/lib/utils.py index 897a70c..403d908 100644 --- a/donpapi/lib/utils.py +++ b/donpapi/lib/utils.py @@ -70,7 +70,10 @@ def parse_file_as_dict(filename: str) -> Dict[str,str]: return arr def parse_credentials_files(pvkfile, passwords_file, nthashes_file, masterkeys_file, username, password, nthash): - pvkbytes = passwords = nthashes = masterkeys = None + pvkbytes = None + passwords = {} + nthashes = {} + masterkeys = [] if pvkfile is not None: try: diff --git a/pyproject.toml b/pyproject.toml index 0837906..712dec0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "donpapi" -version = "2.0.0" +version = "2.0.1" description = "Dumping revelant information on compromised targets without AV detection" authors = ["Login Securite "] readme = "README.md" @@ -28,7 +28,7 @@ dpp = 'donpapi.entry:main' [tool.poetry.dependencies] python = "^3.9" impacket = "^0.11.0" -dploot = "^2.7.1" +dploot = "^2.7.3" rich = "^13.7.0" sqlalchemy = "^2.0.25" termcolor = "^2.4.0"