kerberos fix
This commit is contained in:
parent
a313c77986
commit
fbeb454173
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Reference in New Issue