Skip to content

Instantly share code, notes, and snippets.

@kuujinbo
Created December 31, 2020 00:59
Show Gist options
  • Save kuujinbo/d738bccb05a204c57c7ac539865e3f7b to your computer and use it in GitHub Desktop.
Save kuujinbo/d738bccb05a204c57c7ac539865e3f7b to your computer and use it in GitHub Desktop.
DISA OS STIG; test user registry permissions
<#
# `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