TL;DR How to check for one type of persistence by attackers in AD.
Welcome to Part VII 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
Background
This subject is from mid 2017, however if you don’t follow best practice then it is certainly still possible. In that regard it is rather like Kerberoasting or name poisoning.
It is a post exploit / late stage TTP. That is if attackers are using this then they are most likely in the Domain Dominance stage shown below and are leaving a backdoor for themselves to re-gain access later.
This persistence tactic was described by Will Shroeder, Andy Robbins, and Lee Christensen in their presentation at Blackhat and in a whitepaper. If those names sound familiar it is because they are also the authors of the free tool BloodHound.
The essence of the tactic is that it can be used to
- hide AD objects, such as a user account, so they do not show in Active Directory Users & Computers (ADUC)
- hide a user so that it does not show up in queries
Therefore if you do not specifically check for hidden objects in AD then an attacker could use this technique both to remain invisible from casual observation and evade an audit.
So how does this work?
After an attacker has gained sufficient privilege in AD via various escalation methods they can
- Create an OU with an innocuous name
- Bury it somewhere in the hierarchy where it does not look out of place
- Modify the default ACL on an OU
- Add a Deny statement that prevents Everyone from ListChildren
In ADUC’s ACL editor it might look something like this:
Please note that ‘List contents’ in ADUC is the same privilege as ‘ListChildren’ in PowerShell. Why would an attacker deny both ListChildren and Self with ObjectType 00000000-0000-0000-0000-000000000000? Simple, it makes the ACE they added look much less suspicious to a casual glance in ADUC.
In ADUC the OU will just look as though it is empty. Given an innocuous name such as ‘IT Staging’ it makes sense that the OU would be empty.
Any user accounts, groups, or any other AD objects placed in this OU will not show up in audits, queries, etc. This is actually a bigger, more security relevant point than simply not seeing them in ADUC.
How to find hidden objects
So how do we audit for the presence of hidden objects in AD? The answer is simple. By default AD is in List Child mode. In this mode an attacker has to deny ListChildren in the ACL of the parent OU in order to prevent you from seeing the objects in that OU.
In List Object mode the attacker can deny either ListChildren or ListObject. To confirm that you are still in the default List Child mode simply launch ADSI edit by running adsiedit.msc, connect to Configuration, then navigate to Configuration<your domain>\Services\Windows NT\Directory Services, go to Properties, and check the dsHeuristics attribute. By default it is ‘<not set>’. If it is set to ‘001’ then you are in List Object mode.
So we know that an attacker would have to deny us ListChildren on the parent OU. We can easily query AD for any OUs that match this condition. Querying only OUs is much quicker than querying all objects as doing so would check every user, every group, etc.
I put together a quick and dirty query and tested it out on this vendor’s VM with their pre-built AD.
$OUs = (Get-ADOrganizationalUnit -Filter *).DistinguishedName
ForEach ($OU in $OUs)
{
$OU | Out-File -FilePath C:\Temp\fishy.txt -Append
(Get-Acl $OU).Access | Where {(($_.ActiveDirectoryRights -like “*ListChildren*”) -or ($_.ActiveDirectoryRights -like “*ListObject*”)) -and ($_.AccessControlType -eq “Deny”)} | Out-File C:\Temp\fishy.txt -Append
}
Get-Content C:\Temp\fishy.txt | Select-String “ListChildren” -Context 9
Lo and behold it found one OU out of 280 total in this AD environment. Notice this OU is named something innocuous, buried in the hierarchy, and is under an IT OU where a “Staging” OU makes sense. One would also expect a staging OU to be empty most of the time. It is a clever place to hide.
Note that a devious attacker would probably nest Everyone in an innocuous sounding group and then deny that group ListChildren on the OU. This query will still work. It simply looks for any OU that denies ListChildren to anyone, anyone at all.
AD allows all Authenticated Users, aka Everyone or more commonly simply called Domain Users, to read all objects, all ACLs, everything by default. This is by design. It is called Active Directory for a reason after all. Therefore is it immediately suspicious to find anything that is set to deny read access to anyone. If you find something like this investigate it.
Showing the hidden objects
We can either remove the ACE with the deny statement or we can change it to Allow. Once that is done we see one user and one group.
The user is a member of that group (IT Critical Infrastructure Admins), and that group is in turn nested in the group IT Security Incident Response Team.
IT Security Incident Response Team, that group sounds awfully familiar doesn’t it? Oh yeah, we saw that exact group in Part VI where the vendor said
“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!”
Run our audit again for who can DCSync in the AD environment now that this user & group are not hidden and what do we find?
#Show groups/users who can DCSync
#Also expands all nested groups/users in those groups
#If you don’t have a c:\Temp then just adjust that part of the script
#https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/1522b774-6464-41a3-87a5-1e5633c3fbbb$ErrorActionPreference = “SilentlyContinue”
Import-Module ActiveDirectory
Set-Location AD:
$owner = (Get-Acl (Get-ADDomain).DistinguishedName).owner
Write-Host “$owner owns this object. Owners have implicit privilege to do anything.”$suspects = ((Get-ACL (Get-ADDomain).DistinguishedName).Access | Where {((($_.ActiveDirectoryRights -like “*ExtendedRight*”) -and (($_.ObjectType -eq “1131f6aa-9c07–11d1-f79f-00c04fc2dcd2”) -or ($_.ObjectType -eq “1131f6ad-9c07–11d1-f79f-00c04fc2dcd2”) -or ($_.ObjectType -eq “00000000–0000–0000–0000–000000000000”))) -or ($_.ActiveDirectoryRights -like “*GenericWrite*”) -or ($_.ActiveDirectoryRights -like “*GenericAll*”) -or ($_.ActiveDirectoryRights -like “*WriteDACL*”) -or ($_.ActiveDirectoryRights -like “*WriteOwner*”) -and ($_.AccessControlType -eq “Allow”))}).IdentityReferenceWrite-Host “These groups can execute DCSync. Nested users in those groups listed below:”
$suspects | Out-File C:\Temp\suspects.txt -Append
ForEach($suspect in $suspects)
{
$temp = ($suspect -split “ \ “)[0]
$group = ($temp.Split(“\”)[1])
$members = (Get-ADGroupMember -Identity $group -Recursive).Name
$members | Out-File C:\Temp\suspects.txt -Append
}
Get-Content C:\Temp\suspects.txt | Sort-Object -Unique
Sure enough, our attacker’s hidden user Jack Walker has privileges to DCSync.
If you see this in the wild you would want to initiate your incident response plan. Your domain has been completely breached already.
Summary
Normally I would put a mitigation section. However this is a post exploitation persistence mechanism, so follow best practices and do not let an attacker get the necessary privilege in the first place. If you are paranoid then absolutely audit for OUs matching this description. In fact just add it to the AD audit script that you are probably already running if you are not already checking for it.
As the authors of the whitepaper An Ace up the Sleeve so fittingly pointed out:
“As an offensive researcher, if you can dream it, someone has likely already done it … and that someone isn’t the kind of person who speaks at security cons [conferences].”
This sneaky persistence TTP only works well for an attacker if you are NOT looking for it. If you are looking then it actually makes it much easier to immediately zoom in on the suspicious OU, account, group, etc.
It is something you should be aware of. However it is far from the best, sneakiest persistence tactic for AD. A truly devious attacker would just change a few ACEs in a few ACLs in order to create the sort of privilege escalation path in AD we covered in Part V of this series. The whitepaper’ authors pointed this out as well.
Auditing is important!
Wrapping up
Lastly, I appreciate that the vendor put this in their VM for me to find! After posting a couple of challenges they sadly put a pause on their AD Security for Cyber Security Experts. I am certainly no expert, but I was enjoying the challenges and learning a lot by finding answers to them.
The privilege escalation path I showed in Part V was likely put there on purpose by this vendor. They probably planned on posting a challenge on it but just never got around to it.
References:
https://www.specterops.io/assets/resources/an_ace_up_the_sleeve.pdf
https://docs.microsoft.com/en-us/powershell/module/exchange/add-adpermission?view=exchange-ps
https://blog.paramountdefenses.com/2020/08/putting-pause-on-active-directory.html