Merge pull request #10 from zblurx/dpapi

Store domain backupkey and dpapi secrets in cmedb
main
mpgn 2023-03-13 13:08:55 +01:00 committed by GitHub
commit d9fffd39ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 28 deletions

View File

@ -1180,6 +1180,7 @@ class smb(connection):
if self.args.pvk is not None:
try:
self.pvkbytes = open(self.args.pvk, 'rb').read()
self.logger.success("Loading domain backupkey from {}".format(self.args.pvk))
except Exception as e:
logging.error(str(e))
@ -1191,6 +1192,15 @@ class smb(connection):
self.logger.error(str(e))
if self.pvkbytes is None and self.no_da == None and self.args.local_auth == False:
try:
results = self.db.get_domain_backupkey(self.domain)
except:
self.logger.error("Your version of CMEDB is not up to date, run cmedb and create a new workspace: 'workspace create dpapi' then re-run the dpapi option")
return False
if len(results) > 0:
self.logger.success("Loading domain backupkey from cmedb...")
self.pvkbytes = results[0][2]
else:
try:
dc_target = Target.create(
domain = self.domain,
@ -1211,6 +1221,7 @@ class smb(connection):
backupkey_triage = BackupkeyTriage(target=dc_target, conn=dc_conn)
backupkey = backupkey_triage.triage_backupkey()
self.pvkbytes = backupkey.backupkey_v2
self.db.add_domain_backupkey(self.domain, self.pvkbytes)
else:
self.no_da = False
except Exception as e:
@ -1268,8 +1279,10 @@ class smb(connection):
self.logger.debug("Error while looting credentials: {}".format(e))
for credential in credentials:
self.logger.highlight("[%s][CREDENTIAL] %s - %s:%s" % (credential.winuser, credential.target, credential.username, credential.password))
self.db.add_dpapi_secrets(target.address, 'CREDENTIAL', credential.winuser, credential.username, credential.password, credential.target)
for credential in system_credentials:
self.logger.highlight("[SYSTEM][CREDENTIAL] %s - %s:%s" % (credential.target, credential.username, credential.password))
self.db.add_dpapi_secrets(target.address, 'CREDENTIAL', 'SYSTEM', credential.username, credential.password, credential.target)
try:
# Collect Chrome Based Browser stored secrets
@ -1280,6 +1293,8 @@ class smb(connection):
self.logger.debug("Error while looting browsers: {}".format(e))
for credential in browser_credentials:
self.logger.highlight("[%s][%s] %s %s:%s" % (credential.winuser, credential.browser.upper(), credential.url+' -' if credential.url!= '' else '-', credential.username, credential.password))
self.db.add_dpapi_secrets(target.address, credential.browser.upper(), credential.winuser, credential.username, credential.password, credential.url)
if dump_cookies:
self.logger.info("Start Dumping Cookies")
for cookie in cookies:
@ -1295,7 +1310,7 @@ class smb(connection):
for vault in vaults:
if vault.type == 'Internet Explorer':
self.logger.highlight("[%s][IEX] %s - %s:%s" % (vault.winuser, vault.resource+' -' if vault.resource!= '' else '-', vault.username, vault.password))
self.db.add_dpapi_secrets(target.address, 'IEX', vault.winuser, vault.username, vault.password, vault.resource)
try:
# Collect Firefox stored secrets
@ -1305,7 +1320,7 @@ class smb(connection):
self.logger.debug("Error while looting firefox: {}".format(e))
for credential in firefox_credentials:
self.logger.highlight("[%s][FIREFOX] %s %s:%s" % (credential.winuser, credential.url+' -' if credential.url!= '' else '-', credential.username, credential.password))
self.db.add_dpapi_secrets(target.address, 'FIREFOX', credential.winuser, credential.username, credential.password, credential.url)
@requires_admin
def lsa(self):

View File

@ -76,6 +76,24 @@ class database:
UNIQUE(computerid, userid, name)
)''')
db_conn.execute('''CREATE TABLE "dpapi_secrets" (
"id" integer PRIMARY KEY,
"computer" text,
"dpapi_type" text,
"windows_user" text,
"username" text,
"password" text,
"url" text,
UNIQUE(computer, dpapi_type, windows_user, username, password, url)
)''')
db_conn.execute('''CREATE TABLE "dpapi_backupkey" (
"id" integer PRIMARY KEY,
"domain" text,
"pvk" text,
UNIQUE(domain)
)''')
#db_conn.execute('''CREATE TABLE "ntds_dumps" (
# "id" integer PRIMARY KEY,
# "computerid", integer,
@ -537,3 +555,89 @@ class database:
cur.close()
logging.debug('get_groups(filterTerm={}, groupName={}, groupDomain={}) => {}'.format(filterTerm, groupName, groupDomain, results))
return results
def add_domain_backupkey(self, domain:str, pvk:bytes):
"""
Add domain backupkey
:domain is the domain fqdn
:pvk is the domain backupkey
"""
cur = self.conn.cursor()
cur.execute("SELECT * FROM dpapi_backupkey WHERE LOWER(domain)=LOWER(?)", [domain])
results = cur.fetchall()
if not len(results):
import base64
pvk_encoded = base64.b64encode(pvk)
cur.execute("INSERT INTO dpapi_backupkey (domain, pvk) VALUES (?,?)", [domain, pvk_encoded])
cur.close()
logging.debug('add_domain_backupkey(domain={}, pvk={}) => {}'.format(domain, pvk_encoded, cur.lastrowid))
def get_domain_backupkey(self, domain:str = None):
"""
Get domain backupkey
:domain is the domain fqdn
"""
cur = self.conn.cursor()
if domain is not None:
cur.execute("SELECT * FROM dpapi_backupkey WHERE LOWER(domain)=LOWER(?)", [domain])
else:
cur.execute("SELECT * FROM dpapi_backupkey", [domain])
results = cur.fetchall()
cur.close()
logging.debug('get_domain_backupkey(domain={}) => {}'.format(domain, results))
if len(results) >0:
import base64
results = [(idkey, domain, base64.b64decode(pvk)) for idkey, domain, pvk in results]
return results
def is_dpapi_secret_valid(self, dpapiSecretID):
"""
Check if this group ID is valid.
:dpapiSecretID is a primary id
"""
cur = self.conn.cursor()
cur.execute('SELECT * FROM dpapi_secrets WHERE id=? LIMIT 1', [dpapiSecretID])
results = cur.fetchall()
cur.close()
logging.debug('is_dpapi_secret_valid(groupID={}) => {}'.format(dpapiSecretID, True if len(results) else False))
return len(results) > 0
def add_dpapi_secrets(self, computer:str, dpapi_type:str, windows_user:str, username:str, password:str, url:str=''):
"""
Add dpapi secrets to cmedb
"""
cur = self.conn.cursor()
cur.execute("INSERT OR IGNORE INTO dpapi_secrets (computer, dpapi_type, windows_user, username, password, url) VALUES (?,?,?,?,?,?)", [computer, dpapi_type, windows_user, username, password, url])
cur.close()
logging.debug('add_dpapi_secrets(computer={}, dpapi_type={}, windows_user={}, username={}, password={}, url={}) => {}'.format(computer, dpapi_type, windows_user, username, password, url, cur.lastrowid))
def get_dpapi_secrets(self, filterTerm=None, computer:str=None, dpapi_type:str=None, windows_user:str=None, username:str=None, url:str=None):
"""
Get dpapi secrets from cmedb
"""
cur = self.conn.cursor()
if self.is_dpapi_secret_valid(filterTerm):
cur.execute("SELECT * FROM dpapi_secrets WHERE id=? LIMIT 1", [filterTerm])
elif computer:
cur.execute("SELECT * FROM dpapi_secrets WHERE computer=? LIMIT 1", [computer])
elif dpapi_type:
cur.execute('SELECT * FROM dpapi_secrets WHERE LOWER(dpapi_type)=LOWER(?)', [dpapi_type])
elif windows_user:
cur.execute('SELECT * FROM dpapi_secrets WHERE LOWER(windows_user) LIKE LOWER(?)', [windows_user])
elif username:
cur.execute('SELECT * FROM dpapi_secrets WHERE LOWER(windows_user) LIKE LOWER(?)', [username])
elif url:
cur.execute('SELECT * FROM dpapi_secrets WHERE LOWER(url)=LOWER(?)', [url])
else:
cur.execute("SELECT * FROM dpapi_secrets")
results = cur.fetchall()
cur.close()
logging.debug('get_dpapi_secrets(filterTerm={}, computer={}, dpapi_type={}, windows_user={}, username={}, url={}) => {}'.format(filterTerm, computer, dpapi_type, windows_user, username, url, results))
return results

View File

@ -276,6 +276,52 @@ class navigator(DatabaseNavigator):
self.print_table(data, title='Credential(s) with Admin Access')
def do_dpapi(self, line):
filterTerm = line.strip()
if filterTerm == "":
secrets = self.db.get_dpapi_secrets()
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
elif filterTerm.split()[0].lower() == "browser":
secrets = self.db.get_dpapi_secrets(dpapi_type="MSEDGE")
secrets += self.db.get_dpapi_secrets(dpapi_type="GOOGLE CHROME")
secrets += self.db.get_dpapi_secrets(dpapi_type="IEX")
secrets += self.db.get_dpapi_secrets(dpapi_type="FIREFOX")
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
elif filterTerm.split()[0].lower() == "chrome":
secrets = self.db.get_dpapi_secrets(dpapi_type="GOOGLE CHROME")
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
elif filterTerm.split()[0].lower() == "msedge":
secrets = self.db.get_dpapi_secrets(dpapi_type="MSEDGE")
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
elif filterTerm.split()[0].lower() == "credentials":
secrets = self.db.get_dpapi_secrets(dpapi_type="CREDENTIAL")
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
elif filterTerm.split()[0].lower() == "iex":
secrets = self.db.get_dpapi_secrets(dpapi_type="IEX")
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
elif filterTerm.split()[0].lower() == "firefox":
secrets = self.db.get_dpapi_secrets(dpapi_type="FIREFOX")
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
else:
secrets = self.db.get_dpapi_secrets(filterTerm=filterTerm)
if len(secrets) > 0:
secrets.insert(0,["ID","Host", "DPAPI Type", "Windows User", "Username", "Password", "URL"])
self.print_table(secrets, title='DPAPI Secrets')
def do_creds(self, line):
filterTerm = line.strip()

View File

@ -43,7 +43,7 @@ aioconsole = "^0.3.3"
pywerview = "^0.3.3"
minikerberos = "0.3.5"
aardwolf = "0.2.5"
dploot = "^2.1.14"
dploot = "^2.1.16"
bloodhound = "^1.6.1"
asyauth = "^0.0.12"
masky = "^0.2.0"