kerberos fix

This commit is contained in:
zblurx 2024-07-11 14:38:28 +02:00
parent a313c77986
commit fbeb454173
5 changed files with 65 additions and 35 deletions

View File

@ -21,7 +21,7 @@ class SCCMDump:
def run(self): def run(self):
if self.context.remoteops_allowed: if self.context.remoteops_allowed:
self.logger.display("Dumping SCCM Credentials") 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) sccm_triage = SCCMTriage(target=self.target, conn=self.conn, masterkeys=self.masterkeys, use_wmi=wmi)
sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm() sccmcreds, sccmtasks, sccmcollections = sccm_triage.triage_sccm()
for sccmcred in sccmcreds: for sccmcred in sccmcreds:

View File

@ -42,20 +42,18 @@ class DonPAPICore:
self.lsa_dump = None self.lsa_dump = None
self.dpapi_systemkey = None self.dpapi_systemkey = None
self.hostname = None self.hostname = None
self.dploot_target = Target.create( self.dploot_target = Target.create(
domain=options.domain, domain=options.domain,
username=options.username, username=options.username if options.username is not None else "",
password=options.password, password=options.password if options.password is not None else "",
target=self.host, target=self.host,
lmhash=options.lmhash, lmhash=options.lmhash if options.lmhash is not None else "",
nthash=options.nthash, nthash=options.nthash if options.nthash is not None else "",
do_kerberos=options.k, do_kerberos=options.k or options.aesKey,
no_pass=options.no_pass, no_pass=True,
aesKey=options.aesKey, aesKey=options.aesKey,
use_kcache=options.aesKey or options.k, use_kcache=options.k,
) )
self.logger = DonPAPIAdapter() self.logger = DonPAPIAdapter()
if not self.init_connection(): if not self.init_connection():
return return
@ -258,7 +256,7 @@ class DonPAPICore:
masterkeys += masterkeys_triage.triage_system_masterkeys() masterkeys += masterkeys_triage.triage_system_masterkeys()
except Exception as e: except Exception as e:
self.logger.debug(f"Could not get masterkeys: {e}") self.logger.debug(f"Could not get masterkeys: {e}")
self.masterkeys = masterkeys self.masterkeys += masterkeys
def run(self): def run(self):
self.logger.display("Starting gathering credz") self.logger.display("Starting gathering credz")
@ -305,6 +303,7 @@ class DonPAPICore:
self.logger.fail(f"Error during {collector.__name__}: {e}") self.logger.fail(f"Error during {collector.__name__}: {e}")
# Get yadayadayada # Get yadayadayada
self.logger.verbose("Finished thread")
@property @property
def is_admin(self) -> bool: def is_admin(self) -> bool:

View File

@ -88,25 +88,49 @@ def fetch_all_computers(options):
donpapi_logger.display(f"Collecting every hostnames from {options.domain}") donpapi_logger.display(f"Collecting every hostnames from {options.domain}")
results = None results = None
hostnames = [] hostnames = []
try:
base_dn = 'dc='
base_dn += ",dc=".join(options.domain.split('.'))
ldap_filter = "(objectCategory=computer)" ldap_filter = "(objectCategory=computer)"
attributes = [ attributes = [
"name", "name",
] ]
dc_hostname = None
base_dn = None
try:
ldap_url = f"ldap://{options.domain}" 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: if options.k or options.aesKey:
# Kerberos connection # Kerberos connection
ldap_connection.kerberosLogin( ldap_connection.kerberosLogin(
options.username, options.username if options.username is not None else "",
options.password, options.password if options.password is not None else "",
options.domain, options.domain ,
options.lmhash, options.lmhash if options.lmhash is not None else "",
options.nthash, options.nthash if options.nthash is not None else "",
options.aesKey, options.aesKey if options.aesKey is not None else "",
useCache=options.k, useCache=options.k,
kdcHost=options.dc_ip
) )
else: else:
# NTLM connection # NTLM connection
@ -126,7 +150,9 @@ def fetch_all_computers(options):
donpapi_logger.error(f"Exception while requesting targets: {e}") donpapi_logger.error(f"Exception while requesting targets: {e}")
import traceback import traceback
traceback.print_exc() 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)] results = [r for r in results if isinstance(r, ldapasn1.SearchResultEntry)]
for computer in results: for computer in results:
values = {str(attr["type"]).lower(): attr["vals"][0] for attr in computer["attributes"]} 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: try:
dc_target = Target.create( dc_target = Target.create(
domain=options.domain, domain=options.domain,
username=options.username, username=options.username if options.username is not None else "",
password=options.password, password=options.password,
target=options.domain if options.domain != "" else options.dc_ip, target=options.domain if options.domain != "" else options.dc_ip,
lmhash=options.lmhash, lmhash=options.lmhash,
nthash=options.nthash, nthash=options.nthash,
do_kerberos=options.k, do_kerberos=options.k,
no_pass=options.no_pass, no_pass=True,
aesKey=options.aesKey, aesKey=options.aesKey,
use_kcache=options.aesKey or options.k, 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_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 = 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("-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("--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")) 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: for target in options.target:
if target == "ALL": if target == "ALL":
# Target every computers from domain # Target every computers from domain
if hasattr(options, "domain"): if hasattr(options, "domain") and options.domain != "":
targets.extend(fetch_all_computers(options)) targets.extend(fetch_all_computers(options))
else: else:
donpapi_logger.error("--domain required with --target ALL") donpapi_logger.error("--domain required with --target ALL")
return
else: else:
if os.path.exists(target) and os.path.isfile(target): if os.path.exists(target) and os.path.isfile(target):
with open(target) as target_file: 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): 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 # Recover file
progress_bar.update(task, completed=0 if len(current_target_recovered) == 0 else len(targets) - len(current_target_recovered)) 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: if len(current_target_recovered) > 0:
# finishing targets # finishing targets
targets = current_target_recovered current_targets = current_target_recovered
recover_filename = create_recover_file(targets=targets, dirpath=output_dir, options=options) recover_filename = create_recover_file(targets=targets, dirpath=output_dir, options=options)
donpapi_logger.display(f"Recover file available at {recover_filename}") donpapi_logger.display(f"Recover file available at {recover_filename}")
try: 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] 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): for i in as_completed(future):
target_finished = i.result() target_finished = i.result()
targets.remove(target_finished) current_targets.remove(target_finished)
update_recover_file(recover_file_handle, targets) update_recover_file(recover_file_handle, current_targets)
progress_bar.update(task, advance=1) progress_bar.update(task, advance=1)
except KeyboardInterrupt: except KeyboardInterrupt:
donpapi_logger.error("Caugth Keyboard Interrupt. Gracefully shutdown") donpapi_logger.error("Caugth Keyboard Interrupt. Gracefully shutdown")

View File

@ -70,7 +70,10 @@ def parse_file_as_dict(filename: str) -> Dict[str,str]:
return arr return arr
def parse_credentials_files(pvkfile, passwords_file, nthashes_file, masterkeys_file, username, password, nthash): 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: if pvkfile is not None:
try: try:

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "donpapi" name = "donpapi"
version = "2.0.0" version = "2.0.1"
description = "Dumping revelant information on compromised targets without AV detection" description = "Dumping revelant information on compromised targets without AV detection"
authors = ["Login Securite <contact@login-securite.com>"] authors = ["Login Securite <contact@login-securite.com>"]
readme = "README.md" readme = "README.md"
@ -28,7 +28,7 @@ dpp = 'donpapi.entry:main'
[tool.poetry.dependencies] [tool.poetry.dependencies]
python = "^3.9" python = "^3.9"
impacket = "^0.11.0" impacket = "^0.11.0"
dploot = "^2.7.1" dploot = "^2.7.3"
rich = "^13.7.0" rich = "^13.7.0"
sqlalchemy = "^2.0.25" sqlalchemy = "^2.0.25"
termcolor = "^2.4.0" termcolor = "^2.4.0"