Freigeben über


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

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."