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

image

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