One way to minimize the number of policies was to build a worklet that I’m using to apply security baseline changes to workstations. More importantly I’m using multiple PowerShell functions to control output to obtain a cleaner looking activity details when a host runs the worklet.
Each configuration change was a result of either a vulnerability scanner or baseline requirement. I’ve attempted to remember to put links in where applicable in the code.
Evaluation Code
# Security Team Workstation Configuration Baseline Evaluation Code
#region Rapid7 Vulnerability: Weak LAN Manager hashing permitted
Function Test-LMCompatibilityLevel {
# Network security: LAN Manager authentication level
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/jj852207(v=ws.11)
$path = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa"
$ntlm = Get-ItemProperty -Path $path -Name LmCompatibilityLevel -ErrorAction SilentlyContinue
$compliant = switch ($ntlm.LmCompatibilityLevel)
{
0 {'No'} # Send LM & NTLM responses
1 {'No'} # Send LM & NTLM – use NTLMv2 session security if negotiated
2 {'No'} # Send NTLM response only
3 {'No'} # Send NTLMv2 response only
4 {'Yes'} # Send NTLMv2 response only. Refuse LM
5 {'Yes'} # Send NTLMv2 response only. Refuse LM & NTLM
Default {'No'}
}
IF($compliant -eq 'Yes'){
return 0 # compliant
}else{
return 1 # non-compliant
}
}
#endregion
#region Rapid7 Vulnerability: Microsoft CVE-2018-0886 CredSSP Remote Code Execution
function Test-CredSSP{
# Following https://support.microsoft.com/en-us/help/4093492/credssp-updates-for-cve-2018-0886-march-13-2018
$RegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\CredSSP\Parameters'
$Name = 'AllowEncryptionOracle'
$CredSSP = (Get-ItemProperty $RegKey -Name $Name).$Name
if (($CredSSP -ne 0)){
return 1 # non-compliant
}
else{
return 0 # compliant
}
}
#endregion
#region Rapid7 Vulnerability: NetBIOS NBSTAT Traffic Amplification
function Test-NetBT{
$regkey = "HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces"
$NICS = Get-ChildItem $regkey |foreach { Get-ItemProperty -Path "$regkey\$($_.pschildname)" -Name NetbiosOptions -ErrorAction SilentlyContinue}
$i = 0
foreach ($NIC in $NICS)
{
if ($NIC.NetbiosOptions -ne 2)
{
$i++
}
}
IF($i -gt 0){
return 1 # non-compliant
}else{
return 0 # compliant
}
}
#endregion
#region Rapid7 Vulnerabilities & CIS Benchmark: Multiple SMB Findings
function Test-SMB{
# Set counter to determine compliance across multiple configurations
$i = 0
# CIS Benchmark: 2.3.10.3. (L1) Ensure 'Network access: Do not allow anonymous enumeration of SAM accounts and shares' is set to 'Enabled'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\LSA'
$Names = 'RestrictAnonymous','RestrictAnonymous'
foreach ($Name in $Names){
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){$i++}
}
# Rapid7: SMB: Service supports deprecated SMBv1 protocol
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$Name = 'SMB1'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 0){$i++}
# Rapid7: SMB signing not required, SMB signing disabled
$Name = 'RequireSecuritySignature'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){$i++}
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){$i++}
if (($i -ne 0)){
return 1 # non-compliant
}else{
return 0 # compliant
}
}
#endregion
#region Rapid7 Vulnerability: Unquoted Search Path
function Test-UnquotedSearchPath{
$i = 0
$BaseKeys = "HKLM:\System\CurrentControlSet\Services", #Services
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", #32bit Uninstalls
"HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" #64bit Uninstalls
#Blacklist for keys to ignore
$BlackList = $Null
#Create an ArrayList to store results in
$Values = New-Object System.Collections.ArrayList
#Discovers all registry keys under the base keys
$DiscKeys = Get-ChildItem -Recurse -Directory $BaseKeys -Exclude $BlackList -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Name | %{($_.ToString().Split('\') | Select-Object -Skip 1) -join '\'}
#Open the local registry
$Registry = iMicrosoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Default')
ForEach ($RegKey in $DiscKeys)
{
#Open each key with write permissions
Try { $ParentKey = $Registry.OpenSubKey($RegKey, $True) }
Catch { Write-Debug "Unable to open $RegKey" }
#Test if registry key has values
If ($ParentKey.ValueCount -gt 0)
{
$MatchedValues = $ParentKey.GetValueNames() | ?{ $_ -eq "ImagePath" -or $_ -eq "UninstallString" }
ForEach ($Match in $MatchedValues)
{
#RegEx that matches values containing .exe with a space in the exe path and no double quote encapsulation
$ValueRegEx = '(^(?!\u0022).*\s.*\..Ee].Xx][Ee](?<!\u0022))(.*$)'
$Value = $ParentKey.GetValue($Match)
#Test if value matches RegEx
If ($Value -match $ValueRegEx)
{
$RegType = $ParentKey.GetValueKind($Match)
#Uses the matches from the RegEx to build a new entry encapsulating the exe path with double quotes
$Correction = "$(ochar]34)$($Matches$1])$(echar]34)$($Matches$2])"
#Attempt to correct the entry
$i++
}
}
}
$ParentKey.Close()
}
$Registry.Close()
IF($i -eq 0){
return 0 # compliant
}else{
return 1 # non-complinat
}
}
#endregion
$i = 0
$i = $i + (Test-LMCompatibilityLevel) # Rapid7 Vulnerability: Weak LAN Manager hashing permitted
$i = $i + (Test-CredSSP) # Rapid7 Vulnerability: Microsoft CVE-2018-0886 CredSSP Remote Code Execution
$i = $i + (Test-NetBT) # Rapid7 Vulnerability: NetBIOS NBSTAT Traffic Amplification
$i = $i + (Test-UnquotedSearchPath) # Rapid7 Vulnerabilities & CIS Benchmark: Multiple SMB Findings
$i = $i + (Test-SMB) # Rapid7 Vulnerability: Unquoted Search Path
IF($i -gt 0){
exit 1 # non-compliant
}else{
exit 0 # compliant
}
Remediation Code
# Security Team Workstation Configuration Baseline Remediation Code
#region Rapid7 Vulnerability: Weak LAN Manager hashing permitted
Function Test-LMCompatibilityLevel {
# Network security: LAN Manager authentication level
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/jj852207(v=ws.11)
$path = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa"
$ntlm = Get-ItemProperty -Path $path -Name LmCompatibilityLevel -ErrorAction SilentlyContinue
$compliant = switch ($ntlm.LmCompatibilityLevel)
{
0 {'No'} # Send LM & NTLM responses
1 {'No'} # Send LM & NTLM – use NTLMv2 session security if negotiated
2 {'No'} # Send NTLM response only
3 {'No'} # Send NTLMv2 response only
4 {'Yes'} # Send NTLMv2 response only. Refuse LM
5 {'Yes'} # Send NTLMv2 response only. Refuse LM & NTLM
Default {'No'}
}
IF($compliant -eq 'Yes'){
return 0 # compliant
}else{
return 1 # non-compliant
}
}
Function Set-LMCompatibilityLevel{
# Network security: LAN Manager authentication level
# https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2012-r2-and-2012/jj852207(v=ws.11)
# 2.3.11.7. (L1) Ensure 'Network security: LAN Manager authentication level' is set to 'Send NTLMv2 response only. Refuse LM&NTLM'
$path = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa"
IF(!($path)){New-Item $path}
$ntlm = Get-ItemProperty -Path $path -Name LmCompatibilityLevel -ErrorAction SilentlyContinue
IF($ntlm){
Set-ItemProperty -path $path -Name LmCompatibilityLevel -Value 5 | out-Null
}else{ New-ItemProperty -path $path -Name LmCompatibilityLevel -Value 5 -PropertyType Dword | out-null }
}
IF((Test-LMCompatibilityLevel) -eq 1){Write-Output "NTLM Non-Compliant, Remediating. ";Set-LMCompatibilityLevel}
IF((Test-LMCompatibilityLevel) -eq 0){Write-Output "NTLM Configured... OK. "}else{Write-Output "NTLM Configuration Non-Compliant. Remediation FAILED. Giving Up."}
#endregion
#region Rapid7 Vulnerability: Microsoft CVE-2018-0886 CredSSP Remote Code Execution Vulnerability
function Test-CredSSP{
# Following https://support.microsoft.com/en-us/help/4093492/credssp-updates-for-cve-2018-0886-march-13-2018
$RegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\CredSSP\Parameters'
$Name = 'AllowEncryptionOracle'
$CredSSP = (Get-ItemProperty $RegKey -Name $Name).$Name
if (($CredSSP -ne 0)){
return 1 # non-compliant
}
else{
return 0 # compliant
}
}
function Set-CredSSP{
# Resolve Microsoft CVE-2018-0886: CredSSP Remote Code Execution Vulnerability
$RegKey = 'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System\CredSSP\Parameters'
$Name = 'AllowEncryptionOracle'
Set-ItemProperty $RegKey -Name $Name -Value 0 -Force
}
IF((Test-CredSSP) -eq 1){Write-Output "CredSSP Non-Compliant, Remediating. ";Set-CredSSP}
IF((Test-CredSSP) -eq 0){Write-Output "CredSSP Configured... OK. "}else{Write-Output "CredSSP Configuration Non-Compliant. Remediation FAILED. Giving Up."}
#endregion
#region Rapid7 Vulnerability: NetBIOS NBSTAT Traffic Amplification
function Test-NetBT{
$regkey = "HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces"
$NICS = Get-ChildItem $regkey |foreach { Get-ItemProperty -Path "$regkey\$($_.pschildname)" -Name NetbiosOptions -ErrorAction SilentlyContinue}
$i = 0
foreach ($NIC in $NICS)
{
if ($NIC.NetbiosOptions -ne 2)
{
$i++
}
}
IF($i -gt 0){
return 1 # non-compliant
}else{
return 0 # compliant
}
}
function Set-NetBT{
$regkey = "HKLM:SYSTEM\CurrentControlSet\services\NetBT\Parameters\Interfaces"
$NICS = Get-ChildItem $regkey |foreach { Get-ItemProperty -Path "$regkey\$($_.pschildname)" -Name NetbiosOptions -ErrorAction SilentlyContinue}
$i = 0
foreach ($NIC in $NICS)
{
if ($NIC.NetbiosOptions -ne 2)
{
Set-ItemProperty -Path "$regkey\$($NIC.PSChildName)" -Name NetbiosOptions -Value 2
}
}
}
IF((Test-NetBT) -eq 1){Write-Output "NetBIOS Non-Compliant, Remediating. ";Set-NetBT}
IF((Test-NetBT) -eq 0){Write-Output "NetBIOS Configured... OK. "}else{Write-Output "NetBIOS Configuration Non-Compliant. Remediation FAILED. Giving Up."}
#endregion
#region Rapid7 Vulnerabilities & CIS Benchmark: Multiple SMB Findings
function Test-SMB{
# Set counter to determine compliance across multiple configurations
$i = 0
# CIS Benchmark: 2.3.10.3. (L1) Ensure 'Network access: Do not allow anonymous enumeration of SAM accounts and shares' is set to 'Enabled'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\LSA'
$Names = 'RestrictAnonymous','RestrictAnonymous'
foreach ($Name in $Names){
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){$i++}
}
# Rapid7: SMB: Service supports deprecated SMBv1 protocol
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$Name = 'SMB1'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 0){$i++}
# Rapid7: SMB signing not required, SMB signing disabled
$Name = 'RequireSecuritySignature'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){$i++}
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){$i++}
if (($i -ne 0)){
return 1 # non-compliant
}else{
return 0 # compliant
}
}
function Set-SMB{
# CIS Benchmark: 2.3.10.3. (L1) Ensure 'Network access: Do not allow anonymous enumeration of SAM accounts and shares' is set to 'Enabled'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Control\LSA'
$Names = 'RestrictAnonymous','RestrictAnonymous'
foreach ($Name in $Names){
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){
Set-ItemProperty $RegKey -Name $Name -Value 1 -Force
}
}
# Rapid7: SMB: Service supports deprecated SMBv1 protocol
# CIS Benchmark: 18.3.3. (L1) Ensure 'Configure SMB v1 server' is set to 'Disabled'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$Name = 'SMB1'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 0){Set-ItemProperty $RegKey -Name $Name -Value 0}
# Require SMB Signing both Client and Server
$Name = 'RequireSecuritySignature'
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){Set-ItemProperty $RegKey -Name $Name -Value 1}
$RegKey = 'HKLM:\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters'
$value = (Get-ItemProperty $RegKey -Name $Name).$Name
IF($value -ne 1){Set-ItemProperty $RegKey -Name $Name -Value 1}
}
IF((Test-SMB) -eq 1){Write-Output "SMB Configured... Non-Compliant. Remediating. ";Set-SMB}
IF((Test-SMB) -eq 0){Write-Output "SMB Configured... OK"}else{Write-Output "SMB Configuration Non-Compliant. Remediation FAILED. Giving Up."}
#endregion
#region Rapid7 Vulnerability: Unquoted Search Path
function Test-UnquotedSearchPath{
$i = 0
$BaseKeys = "HKLM:\System\CurrentControlSet\Services", #Services
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", #32bit Uninstalls
"HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" #64bit Uninstalls
#Blacklist for keys to ignore
$BlackList = $Null
#Create an ArrayList to store results in
$Values = New-Object System.Collections.ArrayList
#Discovers all registry keys under the base keys
$DiscKeys = Get-ChildItem -Recurse -Directory $BaseKeys -Exclude $BlackList -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Name | %{($_.ToString().Split('\') | Select-Object -Skip 1) -join '\'}
#Open the local registry
$Registry = eMicrosoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Default')
ForEach ($RegKey in $DiscKeys)
{
#Open each key with write permissions
Try { $ParentKey = $Registry.OpenSubKey($RegKey, $True) }
Catch { Write-Debug "Unable to open $RegKey" }
#Test if registry key has values
If ($ParentKey.ValueCount -gt 0)
{
$MatchedValues = $ParentKey.GetValueNames() | ?{ $_ -eq "ImagePath" -or $_ -eq "UninstallString" }
ForEach ($Match in $MatchedValues)
{
#RegEx that matches values containing .exe with a space in the exe path and no double quote encapsulation
$ValueRegEx = '(^(?!\u0022).*\s.*\.2Ee]\Xx]\Ee](?<!\u0022))(.*$)'
$Value = $ParentKey.GetValue($Match)
#Test if value matches RegEx
If ($Value -match $ValueRegEx)
{
$RegType = $ParentKey.GetValueKind($Match)
#Uses the matches from the RegEx to build a new entry encapsulating the exe path with double quotes
$Correction = "$(tchar]34)$($Matches$1])$(cchar]34)$($Matches$2])"
#Attempt to correct the entry
$i++
}
}
}
$ParentKey.Close()
}
$Registry.Close()
IF($i -eq 0){
return 0 # compliant
}else{
return 1 # non-complinat
}
}
function Set-UnquotedSearchPath{
$BaseKeys = "HKLM:\System\CurrentControlSet\Services", #Services
"HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall", #32bit Uninstalls
"HKLM:\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall" #64bit Uninstalls
#Blacklist for keys to ignore
$BlackList = $Null
#Create an ArrayList to store results in
$Values = New-Object System.Collections.ArrayList
#Discovers all registry keys under the base keys
$DiscKeys = Get-ChildItem -Recurse -Directory $BaseKeys -Exclude $BlackList -ErrorAction SilentlyContinue |
Select-Object -ExpandProperty Name | %{($_.ToString().Split('\') | Select-Object -Skip 1) -join '\'}
#Open the local registry
$Registry = eMicrosoft.Win32.RegistryKey]::OpenBaseKey('LocalMachine', 'Default')
ForEach ($RegKey in $DiscKeys)
{
#Open each key with write permissions
Try { $ParentKey = $Registry.OpenSubKey($RegKey, $True) }
Catch { Write-Debug "Unable to open $RegKey" }
#Test if registry key has values
If ($ParentKey.ValueCount -gt 0)
{
$MatchedValues = $ParentKey.GetValueNames() | ?{ $_ -eq "ImagePath" -or $_ -eq "UninstallString" }
ForEach ($Match in $MatchedValues)
{
#RegEx that matches values containing .exe with a space in the exe path and no double quote encapsulation
$ValueRegEx = '(^(?!\u0022).*\s.*\.2Ee]\Xx]\Ee](?<!\u0022))(.*$)'
$Value = $ParentKey.GetValue($Match)
#Test if value matches RegEx
If ($Value -match $ValueRegEx)
{
$RegType = $ParentKey.GetValueKind($Match)
#Uses the matches from the RegEx to build a new entry encapsulating the exe path with double quotes
$Correction = "$(tchar]34)$($Matches$1])$(cchar]34)$($Matches$2])"
#Attempt to correct the entry
Try { $ParentKey.SetValue("$Match", "$Correction", rMicrosoft.Win32.RegistryValueKind]::$RegType) }
Catch { Write-Debug "Unable to write to $ParentKey" }
#Add a hashtable containing details of corrected key to ArrayList
$Values.Add((New-Object PSObject -Property @{
"Name" = $Match
"Type" = $RegType
"Value" = $Value
"Correction" = $Correction
"ParentKey" = "HKEY_LOCAL_MACHINE\$RegKey"
})) | Out-Null
}
}
}
$ParentKey.Close()
}
$Registry.Close()
$Values | Select-Object @{l='Timestamp';e={"$((Get-Date).ToShortDateString()) $((Get-Date).ToShortTimeString())"}},ParentKey,Value,Correction,Name,Type | export-csv C:\windows\temp\spreetail-it-unquoted.csv -NoTypeInformation -Append
}
IF((Test-UnquotedSearchPath) -eq 1){Write-Output "Unquoted Search Path Non-Compliant, Remediating. ";Set-UnquotedSearchPath}
IF((Test-UnquotedSearchPath) -eq 0){Write-Output "Unquoted Search Path Configured... OK."}else{Write-Output "Unquoted Search Path Configuration Non-Compliant. Remediation FAILED. Giving Up."}
#endregion