Created
December 31, 2020 00:59
-
-
Save kuujinbo/d738bccb05a204c57c7ac539865e3f7b to your computer and use it in GitHub Desktop.
DISA OS STIG; test user registry permissions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
# `Get-Acl` **DOES NOT** display registry permissions like `regedit.exe` GUI | |
# because M$ can **NEVER** create unified, consistent APIs. Unlike the GUI, | |
# `Get-Acl` separates discretionary (access) and system (auditing) ACLs: | |
# https://docs.microsoft.com/en-us/archive/msdn-magazine/2008/november/access-control-understanding-windows-file-and-registry-permissions | |
# | |
# Making things worse, .NET `RegistryRights` and related registry-related | |
# [enum] **DO NOT** include all permissions, and **SEPARATE** effective rights | |
# due to legacy Windows OS generic permission / access rights: | |
# https://superuser.com/questions/1092369/decoding-generic-permissions-access-rights-in-windows | |
# https://docs.microsoft.com/en-us/windows/win32/secauthz/access-mask-format | |
# | |
# M$ does **NOT** provide easy-to-find reference document(s), but some helpful | |
# information is buried at the bottom of this page: | |
# https://docs.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.registryaccessrule.-ctor | |
# | |
# Don't **EVER** try doing **ANY** serious research on a DOD network, which is | |
# impossible due to idiotic URL blocking and trash latency. Unpaid work, but | |
# better than wasting days of effort. | |
# | |
# 2020-12-26 snapshot for reference: | |
# ============================================================================ | |
# Remarks | |
# ============================================================================ | |
# | |
# All registry keys are containers, so the only inheritance flag that is | |
# meaningful for registry keys is the InheritanceFlags.ContainerInherit flag. | |
# If this flag is not specified, the propagation flags are ignored, and only | |
# the immediate key is affected. If the flag is present, the rule is propagated | |
# as shown in the following table. The table assumes there is a subkey S with | |
# child subkey CS and grandchild subkey GS. That is, the path for the | |
# grandchild subkey is S\CS\GS. | |
# | |
# Propagation flags S CS GS | |
# ---------------------------------------------------------------------------- | |
# None X X X | |
# NoPropagateInherit X X | |
# InheritOnly X X | |
# NoPropagateInherit, InheritOnly X | |
# | |
# The pattern for the grandchild subkey governs all subkeys contained by the grandchild subkey. | |
# | |
# For example, if the ContainerInherit flag is specified for inheritanceFlags | |
# and the InheritOnly propagation flag is specified for propagationFlags, this | |
# rule does not apply to the immediate subkey, but does apply to all its | |
# immediate child subkeys and to all subkeys they contain. | |
# | |
# ============================================================================ | |
# Note | |
# ============================================================================ | |
# | |
# Although you can specify the InheritanceFlags.ObjectInherit flag for | |
# inheritanceFlags, there is no point in doing so. For the purposes of access | |
# control, the name/value pairs in a subkey are not separate objects. The | |
# access rights to name/value pairs are controlled by the rights of the subkey. | |
# Furthermore, since all subkeys are containers (that is, they can contain other | |
# subkeys), they are not affected by the ObjectInherit flag. Finally, specifying | |
# the ObjectInherit flag needlessly complicates the maintenance of rules, | |
# because it interferes with the combination of otherwise compatible rules. | |
#> | |
enum GenericRegistryRightsWTF { | |
GENERIC_READ = -2147483648; | |
GENERIC_ALL = 268435456; | |
} | |
function Merge-UserRegistryACLs { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory)][System.Security.AccessControl.RegistrySecurity]$acls | |
,[Parameter(Mandatory)][string]$identityReference | |
); | |
$regAcls = @{}; | |
$acls.Access | where { | |
$_.IdentityReference -eq $identityReference; | |
} | select -Property * -ExcludeProperty IdentityReference | foreach { | |
$_.psobject.properties | foreach { | |
if (!$regAcls.ContainsKey($_.Name)) { | |
$regAcls.Add($_.Name, $_.Value); | |
} else { | |
<# | |
# https://docs.microsoft.com/en-us/dotnet/api/system.security.accesscontrol.accesscontroltype | |
# M$ WTF; cannot combine AccessControlType [enum] (**NOT** ), but backward | |
# assignment, Allow => 0; Deny => 1, so bitwise OR works. | |
# | |
# **ALL** other [enum] properties are flags that allow bitwise combination | |
#> | |
if ($regAcls[$_.Name] -is [enum]) { | |
$regAcls[$_.Name] = $_.Value -bor $regAcls[$_.Name]; | |
} elseif ($regAcls[$_.Name] -is [bool]) { # `IsInherited` | |
$regAcls[$_.Name] = $_.Value -and $regAcls[$_.Name]; | |
} | |
} | |
} | |
} | |
return $regAcls; | |
} | |
function Test-UserRegistryACLs { | |
[CmdletBinding()] | |
param( | |
[Parameter(Mandatory)][System.Security.AccessControl.RegistrySecurity]$acls | |
,[Parameter(Mandatory)][string]$identityReference | |
,[ValidateScript({ | |
([System.Security.AccessControl.AccessControlType] $_).ToString() -eq $_; | |
})][string]$accessControlType = 'Allow' | |
# STIGs => [System.Security.AccessControl.RegistryRights] subset | |
,[Parameter(Mandatory)][ValidateSet( | |
'FullControl' | |
,'ReadKey' | |
)][ValidateScript({ | |
([System.Security.AccessControl.RegistryRights] $_).ToString() -eq $_; | |
})][string]$registryRights | |
,[Parameter(Mandatory)][ValidateSet( | |
'keyAndSubkeys' | |
,'keyOnly' | |
,'subkeysOnly' | |
)][string]$appliesTo | |
,[switch]$isInherited | |
); | |
$regAcls = Merge-UserRegistryACLs -acls $acls -identityReference $identityReference; | |
$pass = $regAcls['AccessControlType'].ToString() -eq $accessControlType; | |
$actualregistryRights = $regAcls['RegistryRights']; | |
switch ($registryRights) { | |
'FullControl' { | |
$fullControl = [System.Security.AccessControl.RegistryRights]::FullControl; | |
$genericCombinedWTF = $fullControl -bor [GenericRegistryRightsWTF]::GENERIC_ALL; | |
$pass = $pass -and ( | |
$actualregistryRights -eq $fullControl ` | |
-or [int]$actualregistryRights -eq [int][GenericRegistryRightsWTF]::GENERIC_ALL ` | |
-or $actualregistryRights -eq $genericCombinedWTF | |
); | |
} 'ReadKey' { | |
$readKey = [System.Security.AccessControl.RegistryRights]::ReadKey; | |
$genericCombinedWTF = $readKey -bor [GenericRegistryRightsWTF]::GENERIC_READ; | |
$pass = $pass -and ( | |
$actualregistryRights -eq $readKey ` | |
-or [int]$actualregistryRights -eq [int][GenericRegistryRightsWTF]::GENERIC_READ ` | |
-or $actualregistryRights -eq $genericCombinedWTF | |
); | |
} | |
} | |
$pass = $pass -and $regAcls['IsInherited'] -eq $isInherited; | |
$actualInheritanceFlags = $regAcls['InheritanceFlags']; | |
$actualPropagationFlags = $regAcls['PropagationFlags']; | |
switch ($appliesTo) { | |
'keyAndSubkeys' { | |
$pass = $pass -and ` | |
$actualInheritanceFlags.HasFlag( | |
[System.Security.AccessControl.InheritanceFlags]::ContainerInherit | |
) -and ` | |
$actualPropagationFlags.HasFlag( | |
[System.Security.AccessControl.PropagationFlags]::None | |
); | |
} 'keyOnly' { | |
$pass = $pass -and ` | |
$actualInheritanceFlags.HasFlag( | |
[System.Security.AccessControl.InheritanceFlags]::None | |
); | |
} 'subkeysOnly' { | |
$pass = $pass -and ` | |
$actualInheritanceFlags.HasFlag( | |
[System.Security.AccessControl.InheritanceFlags]::ContainerInherit | |
) -and ` | |
$actualPropagationFlags -eq [System.Security.AccessControl.PropagationFlags]::InheritOnly; | |
} | |
} | |
return $pass; | |
} | |
#region example | |
# Windows Server 2008 R2 Member Server V1R33, 17 Jun 2020 | |
$v32282 = @( | |
@{ identityReference = 'BUILTIN\Users'; registryRights = 'ReadKey'; appliesTo = 'keyAndSubkeys'; }; | |
@{ identityReference = 'BUILTIN\Administrators'; registryRights = 'FullControl'; appliesTo = 'keyAndSubkeys'; }; | |
@{ identityReference = 'NT AUTHORITY\SYSTEM'; registryRights = 'FullControl'; appliesTo = 'keyAndSubkeys'; }; | |
@{ identityReference = 'CREATOR OWNER'; registryRights = 'FullControl'; appliesTo = 'subkeysOnly'; }; | |
); | |
$aclTest = [ordered]@{ | |
'V-1152' = @{ | |
'HKLM:\SYSTEM\CurrentControlSet\Control\SecurePipeServers\winreg' = @( | |
@{ identityReference = 'BUILTIN\Administrators'; registryRights = 'FullControl'; appliesTo = 'keyAndSubkeys'; }; | |
@{ identityReference = 'S-1-5-32-551'; registryRights = 'ReadKey'; appliesTo = 'keyOnly'; }; | |
@{ identityReference = 'NT AUTHORITY\LOCAL SERVICE'; registryRights = 'ReadKey'; appliesTo = 'keyAndSubkeys'; }; | |
)}; | |
'V-26070' = @{ | |
'HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon' = @( | |
@{ identityReference = 'NT SERVICE\TrustedInstaller'; registryRights = 'FullControl'; appliesTo = 'keyAndSubkeys'; isInherited = $true; }; | |
@{ identityReference = 'NT AUTHORITY\SYSTEM'; registryRights = 'FullControl'; appliesTo = 'keyAndSubkeys'; isInherited = $true; }; | |
@{ identityReference = 'BUILTIN\Administrators'; registryRights = 'FullControl'; appliesTo = 'keyAndSubkeys'; isInherited = $true; }; | |
@{ identityReference = 'BUILTIN\Users'; registryRights = 'ReadKey'; appliesTo = 'keyAndSubkeys'; isInherited = $true; }; | |
)}; | |
'V-32282' = @{ | |
'HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components' = $v32282; | |
}; | |
}; | |
# V-32282, part 2....yeah more trash STIG technical writing.... | |
if ([System.Environment]::Is64BitOperatingSystem) { | |
$aclTest['V-32282'].Add( | |
'HKLM:\SOFTWARE\Wow6432Node\Microsoft\Active Setup\Installed Components', $v32282 | |
); | |
}; | |
$header = @" | |
============================================================================ | |
{0} | |
============================================================================ | |
"@; | |
$aclTest.GetEnumerator() | % { | |
$results = New-Object System.Text.StringBuilder; | |
$vId = $_.Key; | |
$scan = $_.Value; | |
$pass = $true; | |
$details = ''; | |
$scan.GetEnumerator() | % { | |
$rights = $_.Value; | |
$keyPath = $_.Key | |
$acls = Get-Acl $keyPath; | |
$null = $results.AppendFormat($header, $keyPath); | |
:inner | |
foreach ($splat in $rights) { | |
$splat['acls'] = $acls; | |
try { | |
$thisPass = Test-UserRegistryACLs @splat -ErrorAction Stop; | |
} catch { | |
$pass = $false; | |
Write-Warning $_.Exception.Message | |
break inner; | |
} | |
$pass = $pass -and $thisPass; | |
$passText = if ($thisPass) { 'PASS'; } else { 'FAIL'; }; | |
$null = $results.AppendFormat( | |
"({0}) {1}`n", $passText, $splat['identityReference'] | |
); | |
} | |
$allowedAccounts = $rights | % { $_['identityReference']; } | select -Unique; | |
$actualAccounts = $acls.Access | % { $_.IdentityReference; } | select -Unique; | |
$diff = diff -PassThru ` | |
-ReferenceObject $allowedAccounts -DifferenceObject $actualAccounts | | |
where { $_.SideIndicator -eq '=>' }; | |
if ($diff.Count -gt 0) { | |
$pass = $false; | |
$null = $results.Append(@" | |
============================================================================ | |
Unauthorized accounts | |
============================================================================ | |
$($diff -join "`n") | |
"@); | |
} | |
} | |
$allPass = if ($pass) { 'PASS'; } else { 'FAIL'; }; | |
$null = $results.Insert(0, ("{0} ({1})`n" -f $vId, $allPass)); | |
Write-Host $results.ToString(); | |
}; | |
#endregion |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment