Edit: Benjamin reached out and corrected me on a few points, which I’ve updated throughout the post. Importantly, with the ExtraSids (/sids) for the injected Golden Ticket, you need to specify S-1-5-21domain-516 (“Domain Controllers”) and S-1-5-9 (“Enterprise Domain Controllers”), as well as the SECONDARY$ domain controller SID in order to properly slip by some of the event logging.
Benjamin Delpy is constantly adding new features to Mimikatz. In June, he added the ability to include ExtraSids in golden tickets. This was built in coordination with Sean Metcalf‘s work on the subject, and something I talked about here.
Benjamin and Vincent Le Toux also recently added the ability to abuse the MS-DRSR protocol for domain controller replication, in order to recover hashes from a DC without code execution. I touched on this briefly in the post detailing Empire’s v1.2 release (and in a demonstration video) but I wanted to revisit the subject and show how these two new features can be combined into a single attack chain. If you’re interested in Active Directory attacks, be sure to check out Sean’s “Red vs. Blue: Modern Active Directory Attacks & Defense” talk at Derbycon, Friday at 3:00pm. I hear he’ll be dropping some interesting information applicable to this post :)
Sidenote: if you want to compile the newest version of Mimikatz for PowerSploit’s Invoke-Mimikatz, just grab Benjamin’s source code, open it up in Visual Studio, select the “Second_Release_PowerShell” target option and compile for both Win32 and x64.
Then transform the resulting powerkatz.dlls to a base64 string using base64 -w 0 powerkatz.dll in Linux. You can now replace the $PEBytes32 and $PEBytes64 strings at the bottom of Invoke-Mimikatz.ps1. Empire keeps a separately updated version of Invoke-Mimikatz with a few additional tweaks.
Let’s say you’re operating in the following example network:
You land on a machine in the dev.testlab.local domain, and there is tight network filtering from here to the others in the forest; i.e. you can talk to your SECONDARY.dev.testlab.local domain controller but to few machines in other domains. We’ve seen this setup a few times in the field, where an organization keeps the forest root relatively ‘sparse’ and keeps less trusted subsidiaries/groups in a segmented domain.
After some user-hunting and some lateral spread, you end up on workstation WINDOWS3 with domain administrator credentials for dev.testlab.local. From this point historically, you would often compromise/exfil the NTDS.dit of one of DEV’s domain controllers, and then start the process of hopping through the trust mesh. While we were usually successful in cross-domain compromise, this process often took a good a good bit of time and effort. Let’s see how we can use some of these new school techniques to speed up the process.
Step 1: Enumerate the Forest
First let’s do a bit of network and domain situationalf awareness. We can enumerate the current trusts in the forest in a few different ways- my preference is to use PowerView 2.0 and run Get-NetForestDomain or Invoke-MapDomainTrust -LDAP to recursively map all trust relationships in the forest:
This is also possible through Empire with the situational_awareness/network/mapdomaintrusts module.
Step 2: DCSync the Child
Now let’s extract the krbtgt account hash from a dev.testlab.local domain controller. Instead of having to install an agent, we can now use Mimikatz’ DCSync to extract the hash. One thing to note is that you need to specify “<NT4_DOMAINNAME>\krbtgt” for the specified user for this to work properly (you can find the domain shortname easily with whoami or other methods). In this case we’re using DEV.
Here’s how it looks in our environment with Invoke-Mimikatz. Note that you need to use -Command ‘”COMMAND”‘ when running any custom commands through Invoke-Mimikatz (double quotes embedded in single quotes):
And here’s how we can execute the same functionality through Empire:
One nice note- Empire will now parse the DCSync output and save the output into the credential store:
Step 3: ExtraSids to Hop up the Trust
Now let’s use this compromised child DC krbtgt hash to compromise the forest root (and therefore the entire forest). The demo video showed doing this straight from the same original workstation, but in our scenario we run into a problem: we can’t talk directly to the domain controller for the testlab.local root. Happily for us, domain controllers in a forest have to be able to talk to each other for replication and shared authentication, so at a minimum in our scenario, the DC for dev.testlab.local will have communication open to a DC in testlab.local.
To hop up the trust, we need a few pieces of information:
- the krbtgt hash for the child domain (dev.testlab.local), which we just extracted with DCSync
- the SID for dev.testlab.local, also in the DCSync output
- the name of the target DEV user for the ticket
- In this case it’s going to be SECONDARY$, the name of DEV’s domain controller machine account. More on this shortly.
- the fully qualified domain name of the forest root (in our PowerView output)
the SID of the “Enterprise Admins” group of the root
- edit: the SID of the “Domain Controllers” group (S-1-5-21domain-516), the SID of “Enterprise Domain Controllers” (S-1-5-9), and the SID of the SECONDARY$ domain controller (which you can get with ‘Get-NetComputer SECONDARY.dev.testlab.local’ from PowerView), in this case S-1-5-21-4275052721-3205085442-2770241942-1002.
To get the FQDN of the forest root, we could use PowerView with Get-NetForestDomain or Get-NetDomainTrust, or the following one-liner:
Then we need the SID of the forest root. I’m sure there are better ways to do this, but one easy one is to resolve the ‘krbtgt’ account for the domain:
Then we just replace the -502 in the SID
with -519 to get our Enterprise Admins SID for testlab.local (in this case S-1-5-21-456218688-4216621462-1491369290-519) edit: with the -516 “Domain Controllers” SID (in this case S-1-5-21-456218688-4216621462-1491369290-516). The Mimikatz command we’re going to ultimately use to build our trust-hopping ticket is:
kerberos::golden /user:SECONDARY$ /krbtgt:8b7c904343e530c4f81c53e8f614caf7 /domain:dev.testlab.local /sid:S-1-5-21-4275052721-3205085442-2770241942 /sids:S-1-5-21-456218688-4216621462-1491369290-519 /pttEdit: kerberos::golden /user:SECONDARY$ /krbtgt:8b7c904343e530c4f81c53e8f614caf7 /domain:dev.testlab.local /sid:S-1-5-21-4275052721-3205085442-2770241942 /groups:516 /sids:S-1-5-21-456218688-4216621462-1491369290-516,S-1-5-9 /id:S-1-5-21-4275052721-3205085442-2770241942-1002 /ptt
So we have a few options here. We could use Empire to WMI to the DC for dev.testlab.local and then run the credentials/mimikatz/golden_ticket module with the necessary information. For the Golden Ticket creation, we can use the saved krbtgt hash from the DCSync output, setting CredID to 1, the user to SECONDARY$, and the sids to
S-1-5-21-456218688-4216621462-1491369290-519 edit: S-1-5-21-456218688-4216621462-1491369290-516,S-1-5-9:
We could also RDP to the DEV domain controller, use a download cradle to load up Mimikatz, and run our specified command. In either case, we now have administrator access to the domain controller (PRIMARY) for the testlab.local forest root!
Step 4: DCSync the Forest Root
We now have all the privileges needed to compromise the krbtgt hash of the forest root. This time our command will be a bit more complex. One thing we need is the domain NT4 shortname of the forest root. You can use this Gist, or you can translate the username to a SID and back again. In our case, the shortname is TESTLAB.
Here is the command we’ll be using:
lsadump::dcsync /user:TESTLAB\krbtgt /domain:testlab.local
If testlab.local had multiple domain controllers and we wanted to specify a particular one, we could use the /dc:DC.FQDN flag as well. This is how it looks through Empire:
If we want a single Invoke-Mimikatz command to build/inject the Golden Ticket, DCSync the root, and then purge current tickets from the session, we can do that by space separating the double quoted Mimikatz commands:
Invoke-Mimikatz -Command '"kerberos::golden /user:SECONDARY$ /krbtgt:8b7c904343e530c4f81c53e8f614caf7 /domain:dev.testlab.local /sid:S-1-5-21-4275052721-3205085442-2770241942 /sids:S-1-5-21-456218688-4216621462-1491369290-519 /ptt" "lsadump::dcsync /domain:testlab.local /dc:Primary.testlab.local /user:testlab\krbtgt" "kerberos::purge"'
edit: Invoke-Mimikatz -Command '"kerberos::golden /user:SECONDARY$ /krbtgt:8b7c904343e530c4f81c53e8f614caf7 /domain:dev.testlab.local /sid:S-1-5-21-4275052721-3205085442-2770241942 /groups:516 /sids:S-1-5-21-456218688-4216621462-1491369290-516,S-1-5-9 /id:S-1-5-21-4275052721-3205085442-2770241942-1002 /ptt" "lsadump::dcsync /domain:testlab.local /dc:Primary.testlab.local /user:testlab\krbtgt" "kerberos::purge"'
And if the SECONDARY domain controller allows PSRemoting, we don’t even have to RDP, and can perform the entire attack chain from our WINDOWS3 workstation! Because we’re constructing and injecting a new TGT, we don’t have to worry about the Kerberos double-hop problem:
Invoke-Mimikatz -Command '"kerberos::golden /user:SECONDARY$ /krbtgt:8b7c904343e530c4f81c53e8f614caf7 /domain:dev.testlab.local /sid:S-1-5-21-4275052721-3205085442-2770241942 /sids:S-1-5-21-456218688-4216621462-1491369290-519 /ptt" "lsadump::dcsync /domain:testlab.local /dc:Primary.testlab.local /user:testlab\krbtgt" "kerberos::purge"' -ComputerName SECONDARY.dev.testlab.local
edit: Invoke-Mimikatz -Command '"kerberos::golden /user:SECONDARY$ /krbtgt:8b7c904343e530c4f81c53e8f614caf7 /domain:dev.testlab.local /sid:S-1-5-21-4275052721-3205085442-2770241942 /groups:516 /sids:S-1-5-21-456218688-4216621462-1491369290-516,S-1-5-9 /id:S-1-5-21-4275052721-3205085442-2770241942-1002 /ptt" "lsadump::dcsync /domain:testlab.local /dc:Primary.testlab.local /user:testlab\krbtgt" "kerberos::purge"' -ComputerName SECONDARY.dev.testlab.local
Now why did we use the SECONDARY$ account (the domain controller for the child) when building our ticket, as opposed to a normal *-500 Administrator account? edit: And why use the “Domain Controllers” and “Enterprise Domain Controllers” SIDs when creating the ticket? @gentilkiwi explains in the following tweets:
During the execution of our first DCSync, we had to use our current dev.testlab.local Domain Admin credentials, which causes log entries as Delpy describes above. Once we gain the krbtgt hash of the DEV domain controller (through DCSync or other methods), we can be sneakier in attacking the forest root. If we create our Golden Ticket (as we did above) such that the user account in the PAC is the machine account of the DC we’re currently operating from (in our case SECONDARY$), but the ExtraSids contains
the “Enterprise Admins” SID for the forest root edit: the Domain Controllers SID for the forest root and the “Enterprise Domain Controllers” SID, we should be able to DCSync the krbtgt hash of the root without creating additional logs! This is something others have already started to touch on.
Edit: because /id defaults to domain-500, the /user and /id for this ticket won’t match, meaning it will only work for 20 minutes. This is all the time we need, but if you would like it to last longer, you can enumerate the full SID of the SECONDARY.dev.testlab.local domain controller and set that for the /id argument. Note: I haven’t tested this thoroughly as far as log generation, so if the described behavior isn’t accurate, please let me know and I will correct the description.
At this point, with the krbtgt hash of the forest root, we can build Golden Tickets on demand to compromise any machine in the testlab.local forest. By taking advantage of Mimikatz’ new features and Sean’s new work, we can quickly and easily turn the compromise of any domain administrator credentials in the forest into a total forest compromise. One interesting defensive note (that reiterates Microsoft’s description that the domain is not a trust boundary): it’s not sufficient to change all domain passwords and roll the krbtgt account hash of just the root domain (or the compromised domain), you need to roll the krbtgt hash for ALL domains in the forest. Or just: