Public/Get-WeakServicePermission.ps1
|
function Get-WeakServicePermission { <# .SYNOPSIS Finds services whose binary has write-or-higher access granted to unprivileged identities. .DESCRIPTION Enumerates all Win32 services, resolves each service binary path, and checks the file ACL for Allow entries that grant Write, Modify, or FullControl to Everyone, BUILTIN\Users, or NT AUTHORITY\Authenticated Users. Any such entry means a non-administrator could replace the binary and achieve privilege escalation. Returns one object per vulnerable ACE found. Returns nothing if no issues are found. .INPUTS None. Parameters must be supplied directly. .OUTPUTS System.Management.Automation.PSCustomObject .PARAMETER ComputerName The target computer. Defaults to the local machine. .EXAMPLE Get-WeakServicePermission Scans all service binaries on the local machine for weak permissions. .EXAMPLE Get-WeakServicePermission -ComputerName 'Server01' Scans service binaries on Server01 for weak permissions. .NOTES Read-only. Does not modify any system state. Requires read access to the file system ACLs of service binaries. Remote operations require WinRM to be configured on the target machine. Checks the binary file only; does not evaluate parent directory permissions. #> [CmdletBinding()] [OutputType([System.Management.Automation.PSCustomObject])] param ( [Parameter(Mandatory = $false)] [string]$ComputerName = $env:COMPUTERNAME ) $isLocal = ($ComputerName -ieq $env:COMPUTERNAME) -or ($ComputerName -ieq 'localhost') -or ($ComputerName -eq '127.0.0.1') $scan = { # Well-known SIDs for unprivileged identities $weakSids = @( 'S-1-1-0', # Everyone 'S-1-5-32-545', # BUILTIN\Users 'S-1-5-11' # NT AUTHORITY\Authenticated Users ) $writeRights = [System.Security.AccessControl.FileSystemRights]'Write' -bor [System.Security.AccessControl.FileSystemRights]'Modify' -bor [System.Security.AccessControl.FileSystemRights]'FullControl' Get-CimInstance -ClassName Win32_Service | ForEach-Object { $svc = $_ $pathName = $svc.PathName if ([string]::IsNullOrWhiteSpace($pathName)) { return } # Strip surrounding quotes and extract just the executable path $pathName = $pathName.TrimStart('"') if ($pathName -imatch '^([^"]*?\.exe)') { $binaryPath = $Matches[1].Trim() } else { return } if (-not (Test-Path -LiteralPath $binaryPath -PathType Leaf)) { return } try { $acl = Get-Acl -LiteralPath $binaryPath -ErrorAction Stop } catch { Write-Verbose "Could not read ACL for '$binaryPath': $($_.Exception.Message)" return } foreach ($ace in $acl.Access) { if ($ace.AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow) { continue } if (($ace.FileSystemRights -band $writeRights) -eq 0) { continue } try { $sid = $ace.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value } catch { continue } if ($sid -notin $weakSids) { continue } [PSCustomObject]@{ ServiceName = $svc.Name DisplayName = $svc.DisplayName BinaryPath = $binaryPath IdentityReference = $ace.IdentityReference.Value FileSystemRights = $ace.FileSystemRights.ToString() } } } } if ($isLocal) { & $scan | ForEach-Object { [PSCustomObject]@{ ComputerName = $ComputerName ServiceName = $_.ServiceName DisplayName = $_.DisplayName BinaryPath = $_.BinaryPath IdentityReference = $_.IdentityReference FileSystemRights = $_.FileSystemRights } } } else { Invoke-Command -ComputerName $ComputerName -ScriptBlock $scan | ForEach-Object { [PSCustomObject]@{ ComputerName = $ComputerName ServiceName = $_.ServiceName DisplayName = $_.DisplayName BinaryPath = $_.BinaryPath IdentityReference = $_.IdentityReference FileSystemRights = $_.FileSystemRights } } } } |