Forensics: Monitor Active Directory Privileged Groups with PowerShell
Someone just now added Jimmy to the Domain Admins group! How do I know? Because I used PowerShell to check. Let me show you how.
Some of the best customers that I visit get email pages when high value group memberships change. Obviously this is strongly encouraged for IT shops of any size. Of course you can buy products to do this, but here on my blog we build these tools ourselves. It’s more fun and FREE with PowerShell.
Finding Privileged Groups
What if someone adds a user to a group nested in Administrators? How can you tell?
There is a little feature in Active Directory called AdminSDHolder. Every hour by default the PDC will make sure that no one has attempted to escalate the privileges of groups like Server Operators, Account Operators, Administrators, etc. These protected groups have a special attribute: AdminCount = 1. Then, anyone who is a member of these groups, even if nested, gets that same attribute. That means we can find all privileged groups like this:
Get-ADGroup -Filter 'AdminCount -eq 1'
Same thing for users:
Get-ADUser -Filter 'AdminCount -eq 1'
So now we know the high-value targets in the environment.
Finding Group Membership Changes
In a previous post we explored group history using the Get-ADReplicationAttributeMetadata cmdlet in Windows Server 2012. This is one of my favorite cmdlets, especially for forensics. Go read the former post if you want to learn more about that part. You can also watch me explain how this works over at the MVA AD PowerShell videos here; look at module four on forensics.
Now let’s pair it up with what we just learned above. We can loop through all of the privileged groups, and then we’ll query the metadata to see any recent changes to group membership. Even better, we can put a filter on the date value to narrow the results to a specific timeframe.
Show Me Some ‘Shell
Here’s what the code looks like if we put it in a reusable function:
Function Get-PrivilegedGroupChanges {
Param(
$Server = (Get-ADDomainController -Discover | Select-Object -ExpandProperty HostName),
$Hour = 24
)
$ProtectedGroups = Get-ADGroup -Filter 'AdminCount -eq 1' -Server $Server
$Members = @()
ForEach ($Group in $ProtectedGroups) {
$Members += Get-ADReplicationAttributeMetadata -Server $Server `
-Object $Group.DistinguishedName -ShowAllLinkedValues |
Where-Object {$_.IsLinkValue} |
Select-Object @{name='GroupDN';expression={$Group.DistinguishedName}}, `
@{name='GroupName';expression={$Group.Name}}, *
}
$Members |
Where-Object {$_.LastOriginatingChangeTime -gt (Get-Date).AddHours(-1 * $Hour)}
}
# Last 24 hours
Get-PrivilegedGroupChanges
# Last week
Get-PrivilegedGroupChanges -Hour (7*24)| Out-Gridview
# Specific DC, 30 days (24 hours x 30 days)
Get-PrivilegedGroupChanges -Server CVDC1.contoso.com -Hour (24*30)
# Last year of group changes
Get-PrivilegedGroupChanges -Hour (365*24) |
Export-Csv -Path .\PrivGroupMembershipChanges.csv -NoTypeInformation
Explore the code and the properties it returns. Adjust the output to suit your own needs.
Output
In this screenshot we can see that the group nesting stacks up like this:
- Guest
- GroupLevel3
- GroupLevel2
- GroupLevel1
- Domain Admins
The “AdminCount = 1” trick exposes any nesting that may make immediate group membership monitoring elusive. Now we can see that Guest is nested four levels under Domain Admins. The other columns in the report can show you the time of the change, and the DC that originated the change. Then you can review audit logs on that DC to track down who did it.
Also note in the data there is a LastOriginatingDeleteTime. If this date is any newer than the year 1601, then the change could have been a deletion instead of an addition. Study the output, and you’ll see very quickly that this could become very useful in any investigation.
Make It Your Own
Now that you see the power of stitching a few AD cmdlets together it’s your turn. Here are some ideas:
- Pipe the output to a CSV file and attach it to an email notice using Send-MailMessage.
- On a DC or tools server schedule your script to run hourly with a 1 hour query window.
- Test the script by adding a member to a group and see if you can catch it. (Of course, then remove the user afterward.)
- Try adding a user to a group nested in a privileged group. Did you catch that one?
- Create your own view of the output, using filters and fields relevant to your needs.
Be the security hero at your company. Monitor your groups with AD PowerShell. Your boss will love the price tag, too! And you will rest easier knowing that PowerShell has your back.
“An ounce of prevention is worth a pound of cure.”
So today we learned how to monitor your privileged groups for membership changes. What if you could automatically revert any changes to these group members to enforce them and keep the bad guys out? Would you believe that is also free out-of-the-box? PowerShell Desired State Configuration can undo rogue changes to privileged groups. It would require writing a custom DSC resource. But that is a topic for another blog post… :D
Comments
- Anonymous
December 17, 2014
Maybe a little off-topic but somethign I haven't quite figured out: how does SDprop identify critical security principals? For instance in my AD, all the expected built-in principals like Domain Admins, etc are included, but a few groups that were created are returned as well. What identifies them as critical or protected? - Anonymous
December 17, 2014
Never mind, I see it now. It's because one of the built-in principals is a member of the groups we created. Makes sense! - Anonymous
December 17, 2014
Thanks! - Anonymous
December 17, 2014
Awesome as usual, thank you! - Anonymous
December 18, 2014
Great post!!
You can also use restricted groups to maintain your privileged groups. I am not sure how supported that is but it seems to work very well. - Anonymous
December 22, 2014
awesome as always - Anonymous
January 06, 2015
Darn, newest server we have is 2008 R2. "Get-ADReplicationAttributeMetadata" doesn't work. - Anonymous
January 06, 2015
Darn, newest server we have is 2008 R2. "Get-ADReplicationAttributeMetadata" doesn't work. - Anonymous
February 04, 2015
Great post! I wanted to add that I used the 'version' numeric property (which increments with every addition and removal of a principal from the group's membership) to determine if it was an add or delete. Since the first version is an add, the second a delete, the third an add, etc. you can mod the version number to determine which it is. - Anonymous
February 13, 2015
Don't cross the streams (objects and executables)! - Anonymous
February 16, 2015
The comment has been removed - Anonymous
February 23, 2016
Need some help and I know this is not related to this thread, hoping someone can help me. We are flattening our Active Directory structure and right now OUs are delegated with domain groups for delegated ability to write members but were going to move all groups into a single OU and i need to find a way to delegate these domain groups to have the ability to continue to manage those groups but not others. For example if i have 10 domain groups and i need to delegate helpdesk ability to manage 4 of them but operations to manage the other 6 how to write that script? Obviously I am talking about a heck of alot more then 10 groups and if it were that easy I would manually delegate each of them but I am talking about thousands. - Anonymous
October 05, 2017
Perhaps I'm missing something; but is there anyway to tell what sort of change it is? (add/remove)- Anonymous
December 10, 2017
Josh, I was in the blog."Also note in the data there is a LastOriginatingDeleteTime. If this date is any newer than the year 1601, then the change could have been a deletion instead of an addition. Study the output, and you’ll see very quickly that this could become very useful in any investigation."
- Anonymous