Public/Security/Find-SuspiciousGroupMemberships.ps1

function Find-SuspiciousGroupMemberships {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object[]]$Groups,

        [Parameter(Mandatory)]
        [object[]]$Users,

        # ApprovedMembers hash for certain groups; if not present, no member is implicitly approved.
        [hashtable]$ApprovedMembers = @{
            "Domain Admins"     = @("Administrator")
            "Enterprise Admins" = @("Administrator")
            "Schema Admins"     = @("Administrator")
        },

        [int]$NewAccountThresholdDays = 30
    )

    # Pre-build a lookup for users by their DistinguishedName for faster lookups
    $userByDN = @{}
    foreach ($u in $Users) {
        if ($u.DistinguishedName) {
            $userByDN[$u.DistinguishedName] = $u
        }
    }

    $suspiciousFindings = @()
    
    # Define privileged groups and their constraints
    $privilegedGroups = @{
        "Domain Admins"     = @{
            MaxMembers = 5
            RiskLevel  = "Critical"
        }
        "Enterprise Admins" = @{
            MaxMembers = 3
            RiskLevel  = "Critical"
        }
        "Schema Admins"     = @{
            MaxMembers = 2
            RiskLevel  = "Critical"
        }
        "Backup Operators"  = @{
            MaxMembers = 5
            RiskLevel  = "High"
        }
    }

    foreach ($group in $Groups) {
        if ($privilegedGroups.ContainsKey($group.Name)) {
            $groupConfig = $privilegedGroups[$group.Name]

            # Get the approved list for this group if defined, else empty
            $approvedList = $ApprovedMembers[$group.Name]
            if (-not $approvedList) { $approvedList = @() }

            # Check if the group exceeds the maximum expected membership
            if ($group.Members.Count -gt $groupConfig.MaxMembers) {
                $suspiciousFindings += [PSCustomObject]@{
                    GroupName    = $group.Name
                    Finding      = "Excessive Members"
                    Details      = "Group has $($group.Members.Count) members, expected max $($groupConfig.MaxMembers)"
                    RiskLevel    = $groupConfig.RiskLevel
                    TimeDetected = Get-Date
                }
            }

            # Check each member in the group
            foreach ($memberDN in $group.Members) {
                # Attempt to retrieve the member from the lookup
                $member = $userByDN[$memberDN]
                if ($member) {
                    # If the member is not on the approved list, consider it suspicious
                    if (-not ($approvedList -contains $member.SamAccountName)) {
                        $finding = [PSCustomObject]@{
                            GroupName    = $group.Name
                            MemberName   = $member.SamAccountName
                            Finding      = "Unauthorized Member"
                            Details      = "Member not in approved list"
                            RiskLevel    = $groupConfig.RiskLevel
                            TimeDetected = Get-Date
                        }
                        
                        # If the account was recently created, escalate severity
                        if ($member.Created -gt (Get-Date).AddDays(-$NewAccountThresholdDays)) {
                            $finding.Finding = "Recently Created Account in Privileged Group"
                            $finding.RiskLevel = "Critical"
                        }

                        # If the account is disabled, flag this
                        if ($member.Enabled -eq $false) {
                            $finding.Finding = "Disabled Account in Privileged Group"
                        }
                        
                        $suspiciousFindings += $finding
                    }
                }
                else {
                    # Could not find the user in the provided list - this might also be suspicious,
                    # or could indicate the user data is incomplete. Consider logging a warning.
                }
            }
        }
    }

    return $suspiciousFindings
}