In a large environment, correctly managing Privileged Access can be complicated and complex. Especially in a Windows Server environment, administrators can easily add other users and groups to the server’s Local Administrator group. This can lead to privilege escalation, privilege abuse, and data loss. So it’s important to monitor local administrators on servers. I put together this script to audit local administrator members across all the servers.
Goals
The goals I had for this script were as follows:
- The script can be run from one central location and audit all servers in the domain.
- One output that could be parsed at any time for rogue accounts.
- As dynamic as possible.
Powershell Script
This audit script is fairly simple. There’s an array of users and group names that are whitelisted because they are valid administrator accounts and groups.
We pull the server machine names from Active Directory and then pull each server’s Local Administrator members using Get-NetLocalGroup. We take the output from that and parse it in Check-Members. Any unexpected accounts that are members of the group are logged with a warning.
### Configuration ### $Logdir = "C:\scripts\logs\" $Logfile = "LocalAdmin-Audit-$(get-date -Format FileDateTimeUniversal).log" $Logfile = Join-Path $Logdir $Logfile $Tab = [char]9 # Whitelist Users and Groups $validadmins = "LOCALUS\Domain Admins", "Domain Admins", "Enterprise Admins", "LOCALUS\Server-Local-Admins", "LOCALUS\SVC_DEPLOY", "LOCALUS\SVC_CS_Admin", "LOCALUS\SVC_SQL", "LOCALUS\SVC_Manager", "LOCALUS\Web_Admin", "LOCALUS\SVC_SQL_AGENT" ### Functions ### Function LogWrite { Param ([string]$logstring) if (!(Test-Path $Logdir)) { New-Item -ItemType Directory -Force -Path $Logdir} $d = Get-Date -Format “dd/MM/yyyy HH:mm:ss” $logline = "$d $Tab $logstring" Add-content $Logfile -value $logline } Function Get-NetLocalGroup { [cmdletbinding()] Param( [Parameter(Position=0)] [ValidateNotNullorEmpty()] [object[]]$Computername=$env:computername, [ValidateNotNullorEmpty()] [string]$Group = "Administrators", [switch]$Asjob ) Write-Verbose "Getting members of local group $Group" #define the scriptblock $sb = { Param([string]$Name = "Administrators") $members = net localgroup $Name | where {$_ -AND $_ -notmatch "command completed successfully"} | select -skip 4 New-Object PSObject -Property @{ Computername = $env:COMPUTERNAME Group = $Name Members=$members } } #end scriptblock #define a parameter hash table for splatting $paramhash = @{ Scriptblock = $sb HideComputername=$True ArgumentList=$Group } if ($Computername[0] -is [management.automation.runspaces.pssession]) { $paramhash.Add("Session",$Computername) } else { $paramhash.Add("Computername",$Computername) } if ($asjob) { Write-Verbose "Running as job" $paramhash.Add("AsJob",$True) } #run the command Invoke-Command @paramhash | Select * -ExcludeProperty RunspaceID } #end Get-NetLocalGroup function Check-Members($admingroup){ $h = $admingroup.Computername $m = $admingroup.Members Write-Host "Admins for $h" foreach ($m1 in $m) { if($validadmins -contains $m1){ Write-Host $m1 -ForegroundColor Green }else{ if ($m1 -match "-Admin") { Write-Host $m1 -ForegroundColor Green } else { Write-Host $m1 -ForegroundColor Yellow LogWrite "$h $Tab WARNING $Tab Unexpected Admin Account found - $m1" } } } } ### Main ### $servers = Get-ADComputer -Filter * foreach ($s in $servers) { $server = $s.DNSHostName $ping = Test-Connection $server -Count 1 -Quiet LogWrite "$server $Tab Checking $server" if ($ping){ Write-Host " $server " -BackgroundColor White -ForegroundColor DarkBlue $admins = Get-NetLocalGroup $server Check-Members $admins }else{ Write-Warning "Could not connect to $server" LogWrite "$server $Tab Could not connect to $server" } Write-Host " " -BackgroundColor White -ForegroundColor DarkBlue } ### End Script ###
The script will generate a log output for every run. Servers that can not be contacted will be logged. Unexpected Admin accounts are logged as a warning.
09/11/2019 02:58:20 SERVERSSI01 Checking SERVERSSI01 09/11/2019 02:58:20 SERVERAVS02 Checking SERVERAVS02 09/11/2019 02:58:21 SERVERAVS02 WARNING Unexpected Admin Account found - LOCALUS\joel.robinson 09/11/2019 02:58:21 SERVERANL01 Checking SERVERANL01 09/11/2019 02:58:25 SERVERAPP02 Checking SERVERAPP02 09/11/2019 02:58:25 SERVERAPP02 Could not connect to SERVERAPP02 09/11/2019 02:58:29 SERVERSQL01 Checking SERVERSQL01
You could load this output into your SIEM, or configure the script to log directly to a SIEM or other data lake.
References
The Get-NetLocalGroup function was originally published on powershell.org in 2013.