Forum Discussion
Get Privileged User Accounts and then associate those names to their AD Groups.... ???
Hi, Scott.
Using "adminCount = 1" is unreliable since the SDProp process sets this value when someone is added to a privileged group (directly or indirectly) but it does not clear it again if the person is later removed.
Using adminCount will only result in your audit becoming less trustworthy over time.
The only way to accurately perform this kind of audit is to enumerate the transitive memberships of the groups you're interested in, and after that do other follow-on tasks such as looking up the users and their group memberships.
Cheers,
Lain
Hi Lain,
I actually don't have the Group Names. I need to know elevated privilege accounts and then associate those users to groups....
I'm not a Powershell expert so I am not sure what the string would look like. Would you be so kind as to let me know? Much appreciated your answer previously.
Scott
- LainRobertsonSep 02, 2023Silver Contributor
Hi, Scott.
The privileged groups can be found listed here:
Have you started writing your script, or are you looking for someone in here to write it for you?
Cheers,
Lain
- Scott_AZSep 03, 2023Copper Contributor
Hello Lain,
First, thank you for your reply. Although I do know a fairly easy way to find the groups a user belongs to in the script below (though there is a slight hiccup with two word service accounts) it does suit my needs. However..... if I could pull everything together in a script (Privileged Accounts and the accounts group memberships in csv format....) that would be perfect.
So that is where I am lost right now and yes, I could use help with writing the script.
Scott
PS -- I found the following script that give me the memberships of a given user....Add-Type -AssemblyName System.DirectoryServices.AccountManagement $username = read-host -prompt "Enter a username" $ct = [System.DirectoryServices.AccountManagement.ContextType]::Domain $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($ct, $username) $groups = $user.GetGroups() foreach($i in $groups){ $i.SamAccountName }
- LainRobertsonSep 03, 2023Silver Contributor
Hi, Scott.
The problem you're trying to solve is moderately complex, and that script is a very long way for getting you to completion.
Logically, the problem you've outlined requires the following approach:
I've already provided the URL to the Active Directory protected groups as well as explained why the adminCount attribute is not trustworthy. These will serve as the prerequisites for the following solution.
I've decided to implement the logical flow from above as two "scripts".
First script
This script does all of the heavy lifting and almost provides the complete end-to-end solution.
It's easier to say what it does not do, which is flatten out the object stream into a CSV file - that's what the second "script" (which is really just a single piped command) will do.
One reason for this is that you can easily extend this first script to capture other supporting data, which I've done as shown in the output, where you can see other useful attributes have been included.
Another reason is that in returning objects rather than just dumping to a CSV file, it can be used within pipelines for further processing by other scripts/commands.
I named this script Get-ADPrivilegedIdentities.ps1 but you can name it whatever you like. I specifically avoided referencing "Users" since it will show any member type that isn't a group, meaning computers could show up.
One useful behaviour of the script is that it automatically excludes duplicate references to the same identity, since there's no value in listing the same identity more than once and it just makes the output harder to process.
Get-ADPrivilegedIdentities.ps1
[cmdletbinding()] Param() try { #region Global variables. $RootDSE = [adsi]"LDAP://RootDSE"; $Server = $RootDSE.dNSHostName[0]; $Domain = [adsi]"LDAP://$Server/$($RootDSE.defaultNamingContext)"; $DomainSid = [System.Security.Principal.SecurityIdentifier]::new($Domain.objectSid[0], 0).Value; $ProcessedUsers = [System.Collections.Generic.List[int]]::new(); $MemberOf = [System.Collections.Generic.List[string]]::new(); Write-Verbose -Message "Connected to $Server ..."; #endregion # Protected groups obtained from https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/plan/security-best-practices/appendix-c--protected-accounts-and-groups-in-active-directory#protected-accounts-and-groups-in-active-directory-by-operating-system. $ProtectedGroups = @( "S-1-5-32-548" # Account Operators , "S-1-5-32-544" # Administrators , "S-1-5-32-551" # Backup Operators , "$DomainSid-512" # Domain Admins , "$DomainSid-519" # Enterprise Admins , "S-1-5-32-550" # Print Operators , "$DomainSid-518" # Schema Admins , "S-1-5-32-549" # Server Operators ) #region Set up a directory searcher for each level of expansion. $Searcher = [adsisearcher]::new(); $Searcher.SearchRoot = $Domain; $Searcher.PropertiesToLoad.AddRange(@("distinguishedName")); $MemberSearcher = [adsisearcher]::new(); $MemberSearcher.SearchScope = [System.DirectoryServices.SearchScope]::Base; $MemberSearcher.PropertiesToLoad.AddRange(@("msds-memberTransitive", "name")); $ObjectSearcher = [adsisearcher]::new(); $ObjectSearcher.SearchScope = [System.DirectoryServices.SearchScope]::Base; $ObjectSearcher.PropertiesToLoad.AddRange(@("accountExpires", "distinguishedName", "msds-memberOfTransitive", "msDS-UserPasswordExpiryTimeComputed", "name", "objectClass", "objectGUID", "primaryGroupID", "userAccountControl", "userPrincipalName" )); #endregion $ProtectedGroups | ForEach-Object { Write-Verbose -Message "`nProcessing $_ ..."; #region Convert the SID string template to a byte array first, and then to a useable LDAP search string. $SecurityIdentifier = [System.Security.Principal.SecurityIdentifier]::new($_); $SidAsBytes = [byte[]]::new($SecurityIdentifier.BinaryLength); $SecurityIdentifier.GetBinaryForm($SidAsBytes, 0); $Searcher.Filter = "objectSid=\$([System.BitConverter]::ToString($SidAsBytes).Replace("-", "\"))"; Write-Verbose -Message "Search filter = $($Searcher.Filter)"; #endregion # Find the privileged group's distinguishedName. $Searcher.FindOne() | ForEach-Object { $dn = $_.Properties["distinguishedName"]; Write-Verbose -Message "Enumerating memberships from $dn ..."; try { #region Perform a base search using the distinguishedName of the privileged group to retrieve the transitive memberships. $MemberSearcher.SearchRoot = [adsi]"LDAP://$Server/$dn"; $MemberSearcher.Filter = "(objectClass=*)"; Write-Verbose -Message "Member search root = $($MemberSearcher.SearchRoot.Path)"; $MemberSearcher.FindOne() | ForEach-Object { #region Iterate through each member of the privileged group. foreach ($Entry in $_.Properties["msds-memberTransitive"]) { #region Check we haven't already processed this user, and skip them if we have. if ($ProcessedUsers.Contains(($Checksum = $Entry.GetHashCode()))) { Write-Verbose -Message "Skipping $Entry."; continue; } else { Write-Verbose -Message "Enumerating transitive ""memberOf"" references for: $Entry."; $ProcessedUsers.Add($Checksum); } #endregion #region Perform a base search against the member to retrieve a transitive list of groups it is a member of $ObjectSearcher.SearchRoot = [adsi]"LDAP://$Server/$Entry"; if ($null -ne ($Instance = $ObjectSearcher.FindOne())) { if (-not ($ObjectClass = $Instance.Properties["objectClass"][$Instance.Properties["objectClass"].Count-1]).Equals("group")) { #region Fetch the primary group details first. $PrimaryGroup = [adsi]"LDAP://$Server/<SID=$DomainSid-$($Instance.Properties["primaryGroupID"][0])>"; $MemberOf.Add($PrimaryGroup.Properties["distinguishedName"][0]); #endregion $MemberOf.AddRange([string[]] ($Instance.Properties["msds-memberOfTransitive"])); $MemberOf.Sort(); [PSCustomObject] @{ objectGUID = [guid]::new($Instance.Properties["objectGUID"][0]); objectClass = $ObjectClass; enabled = ($Instance.Properties["userAccountControl"][0] -band 0x2) -eq 0; name = $Instance.Properties["name"][0]; userPrincipalName = $Instance.Properties["userPrincipalName"][0]; neverExpires = $Instance.Properties["userPrincipalName"][0] -in (0, [int64]::MaxValue); passwordNeverExpires = $Instance.Properties["msDS-UserPasswordExpiryTimeComputed"][0] -eq [int64]::MaxValue; distinguishedName = $Instance.Properties["distinguishedName"][0]; memberOfTransitive = $MemberOf.ToArray(); } $MemberOf.Clear(); $PrimaryGroup.Dispose(); } } #endregion } #endregion } #endregion } catch [System.UnauthorizedAccessException] { Write-Warning -Message "Access denied on ""$dn"""; } catch { throw; } finally { if ($MemberSearcher.SearchRoot) { $MemberSearcher.SearchRoot.Dispose(); } } } } } catch { throw; } finally { $ProcessedUsers.Clear(); if ($Seacher) { $Searcher.Dispose(); } if ($Domain) { $Domain.Dispose(); } if ($RootDSE) { $RootDSE.Dispose(); } }Example output
Second script
This isn't a script at all, but just a single piped command.
In any case, this command simply takes the output from Get-ADPrivilegedIdentities.ps1, chooses a few attributes of interest and pipes it out to a CSV file.
Command
.\Get-ADPrivilegedIdentities.ps1 | ForEach-Object { $DirectoryObject = $_; $_.memberOfTransitive | ForEach-Object { [PSCustomObject] @{ objectClass = $DirectoryObject.objectClass; distinguishedName = $DirectoryObject.distinguishedName; memberOf = $_; } } } | Export-Csv -NoTypeInformation -Path "D:\Data\Temp\Forum\forum.csv";Example output
Cheers,
Lain