To be able to audit Applocker rules created or hunt for human error, I wanted to have a simple way to score Applocker rules.
Are they good or evil? Are they tuned and specific enough to allow or block something?
Is there any rule that is more permissive or not well designed due to a human error?
- Issue
My Google-fu didn’t find anything relevant to help me score Applocker rules 😦
- Solution
How are rules scored?
I’ve tried to score various aspects of the rule, based on the type (Publisher, Path or Hash), the identity targeted, the action (Allow or Deny) and the conditions.
The code aims at finding and identifying some rules that are more permissive than others, so that it’s easier to spot them and pay attention to these rules.
All the functions scoring rules follow a simple principle. The higher score, the more dangerous/permissive the rule is actually.
There’s a limitation, the code doesn’t evaluate at all the Exceptions and worse, having conditions could have a negative impact on the score (lowering it).
The other limitation is that the code assumes that there isn’t an administrator introducing malicious rules.
How does it perform? Show me examples !
Import-Module -Name Applocker -SkipEditionCheck Import-Module ~/Documents/ApplockerScore.psm1 -Verbose Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts'
You can see that there’s tab completion for the parameter. You just need to begin by the Type to be able to display the rule names on the 2nd parameter using tab completion.
The default rule that allows anything for an admin is score over 1000. It means it’s too permissive. It’s a Bypass rule for anything (a wildcard) for the members of the local Administrators group. It’s not the highest score you can get.
cd\ Import-Module -Name Applocker -SkipEditionCheck Import-Module ~/Documents/ApplockerScore.psm1 Get-Help Get-ApplockerPolicyRuleScore -Full $rule = Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts' -Verbose Write-Verbose -Message "Final score for '$($rule.Name)' is $($rule.Score)" -Verbose $rule.Object $rule.Object | gm
It shows how the score is calculated and what was evaluated to score the rule.
There’s an embedded object property that is its XML definition converted back to its original object type.
Besides simple examples, can it be used against exported Applocker policies to XML files?
Yes, it can. You just need to use the internal private functions and load them.
Here’s a practical example on Ultimate AppLocker ByPass List from Oddvar Moe
Let’s say, you’ve downloaded his Github repo as a zip file, extracted it and loaded the internal functions.
You can now do the following:
Get-ChildItem -Path '~\Downloads\UltimateAppLockerByPassList-master\UltimateAppLockerByPassList-master\AppLocker-BlockPolicies\*.xml' | | |
ForEach-Object { | |
([xml](Get-Content -Path $_.FullName -ReadCount 0)).AppLockerPolicy.RuleCollection.ChildNodes | | |
Out-GridView -PassThru | | |
ForEach-Object { | |
$TypeScore = Get-ApplockerRuleTypeScore $_.OuterXML -Verbose | |
$ActionSore = Get-ApplockerRuleActionScore $_.OuterXML -Verbose | |
$IdScore = Get-ApplockerRuleUserOrGroupSidScore $_.OuterXML -Verbose | |
$ConditionScore = Get-ApplockerRuleConditionsScore $_.OuterXML -Verbose | |
[PSCustomObject]@{ | |
Id = $_.Id | |
Name = $_.Name | |
Score = ($TypeScore+$IdScore+$ConditionScore)*$ActionSore | |
XML = $_.OuterXML | |
} | |
} | |
} |
Here’s an example on the rule named ‘Signed by Skype’:
Here’s a 2nd example on the rule about MSHTA:
Where’s the code?
It’s stored in a psm1 file uploaded as gist on GitHub.com:
#Requires -Version 3.0 | |
#region Score the identity | |
Function Get-ApplockerRuleUserOrGroupSidScore { | |
[CmdletBinding()] | |
[OutputType([int32])] | |
Param( | |
[Parameter(Mandatory)] | |
[Alias('UserOrGroupSid','SID')] | |
$InputObject | |
) | |
Begin {} | |
Process { | |
$InputObject | | |
ForEach-Object { | |
Switch -Regex ($_) { | |
'S-1-1-0' { # EveryOne | |
Write-Verbose -Message 'Applocker score: 50 ; Everyone' | |
50 ; break | |
} | |
'S-1-5-32-544' { # Administrators | |
Write-Verbose -Message 'Applocker score: 10 ; Administrators' | |
10 ; break | |
} | |
'S-1-5-32-545' { # Users | |
Write-Verbose -Message 'Applocker score: 10 ; Users' | |
10 ; break | |
} | |
# S-1-5-11 # Authenticated Users | |
# S-1-5-32-546 # Guests | |
# S-1-5-32-547 # Power Users | |
default { | |
Write-Verbose -Message 'Applocker score: Id is specific' | |
10 | |
} | |
} | |
} | |
} | |
End {} | |
} | |
#endregion | |
#region Score the rule type | |
Function Get-ApplockerRuleTypeScore { | |
[CmdletBinding()] | |
[OutputType([int32])] | |
Param( | |
[Parameter(Mandatory)] | |
[string[]]$InputObject | |
) | |
Begin {} | |
Process { | |
$InputObject | | |
ForEach-Object { | |
Switch -Regex ($_) { | |
'^<FilePath(Rule|Condition)' { Write-Verbose -Message 'Applocker score: 100 ; Path type' ; 100 } | |
'^<FilePublisher(Rule|Condition)' { Write-Verbose -Message 'Applocker score: 50 ; Path publisher' ; 50} | |
'^<FileHash(Rule|Condition)' {Write-Verbose -Message 'Applocker score: 50 ; Path Hash' ; 50} | |
default {} | |
} | |
} | |
} | |
End {} | |
} | |
#endregion | |
#region Score the rule action | |
Function Get-ApplockerRuleActionScore { | |
[CmdletBinding()] | |
[OutputType([int32])] | |
Param( | |
[Parameter(Mandatory)] | |
[string[]]$InputObject | |
) | |
Begin {} | |
Process { | |
$InputObject | | |
ForEach-Object { | |
Switch -Regex ($_) { | |
'\sAction="Allow"' { | |
Write-Verbose -Message 'Applocker score: *1 ; Action is Allow' | |
1 | |
} | |
'\sAction="Deny"' { | |
Write-Verbose -Message 'Applocker score: /10 ; Action is Deny' | |
0.1 | |
} | |
default {} | |
} | |
} | |
} | |
End {} | |
} | |
#endregion | |
#region Score the rule conditions | |
Function Get-ApplockerRuleConditionsScore { | |
[CmdletBinding()] | |
[OutputType([int32])] | |
Param( | |
[Parameter(Mandatory)] | |
[string[]]$InputObject | |
) | |
Begin {} | |
Process { | |
$InputObject | | |
ForEach-Object { | |
($( | |
Switch -Regex ($_) { | |
'^<FilePath(Rule|Condition)' { | |
Write-Verbose -Message 'Applocker score: Path type' | |
# Depth: count the occurrence of '\' | |
$depth = 110 - (($_ -split '\\').Count)*10 | |
Write-Verbose -Message "Depth score is $($depth)" | |
$depth | |
# Evaluate if there's a wildcard in the path | |
if ($_ -match '\\\*\\') { | |
50 | |
} | |
Switch -Regex ($_) { | |
# Just a specific extension | |
# .exe, .com *.msi, msp mst ps1 cmd bat vbs js appx msix dll ocx | |
'\sPath="\*\.([eE][xX][eE]|[cC][oO][mM]|[mM][sS][iI]|[mM][sS][pP]|[mM][sS][tT]|[pP][sS]1|[cC][mM][dD]|[bB][aA][tT]|[vV][bB][sS]|[jJ][sS])"\s' { | |
Write-Verbose -Message "Path score: 500 (only a *.ext)" | |
500 | |
} | |
# Path letter | |
'\sPath="(c|C):\\' { | |
Write-Verbose -Message "Path score: 100 (Path on C: drive)" | |
100 | |
} | |
'\sPath="[abABd-zD-Z]:\\' { | |
Write-Verbose -Message "Path score: 100 (Path not on C: drive)" | |
50 | |
} | |
# Extension: just a wildcard | |
'\sPath="\*\.\*"\s' { # *.* | |
Write-Verbose -Message "Path score: 1000 (only a *.*)" | |
1000 ; break | |
} | |
'\sPath="\*"\s' { # * | |
Write-Verbose -Message "Path score: 1000 (only a *)" | |
1000 ; break | |
} | |
default {} | |
} | |
} | |
'^<FilePublisher(Rule|Condition)' { | |
Write-Verbose -Message 'Applocker score: Path publisher' | |
Switch -Regex ($_) { | |
'PublisherName="\*"\sProductName="\*"\sBinaryName="\*"\>\<BinaryVersionRange\sLowSection="0\.0\.0\.0"\sHighSection="\*"' { | |
Write-Verbose -Message "Publisher score: 1000 (default Appx PublisherName=*)" | |
1000 ; break | |
} | |
'ProductName="\*"\sBinaryName="\*"\>\<BinaryVersionRange\sLowSection="\*"\sHighSection="\*"' { | |
Write-Verbose -Message "Publisher score: 500 (only a PublisherName)" | |
500 ; break | |
} | |
'\sBinaryName="\*"\>\<BinaryVersionRange\sLowSection="\*" HighSection="\*"' { | |
Write-Verbose -Message "Publisher score: 500 (PublisherName and ProductName)" | |
200 ; break | |
} | |
'BinaryVersionRange\sLowSection="\*"\sHighSection="\*"' { | |
Write-Verbose -Message "Publisher score: 100 (PublisherName and ProductName and FileName)" | |
100 ; break | |
} | |
default { | |
Write-Verbose -Message "Publisher score: 50 (default)" | |
50 | |
} | |
} | |
} | |
'^<FileHash(Rule|Condition)' { | |
Write-Verbose -Message "Hash score: 50" | |
50 | |
} | |
default {} | |
} | |
) | Measure-Object -Sum).Sum | |
} | |
} | |
End {} | |
} | |
#endregion | |
#region Convert to Object from Xml | |
Function Get-ApplockerRuleObjectFromXml { | |
[CmdletBinding()] | |
[OutputType([int32])] | |
Param( | |
[Parameter(Mandatory)] | |
[string[]]$InputObject | |
) | |
Begin {} | |
Process { | |
$InputObject | | |
ForEach-Object { | |
$o = $_ | |
Switch -Regex ($_) { | |
'^<FilePathRule' { | |
try { | |
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FilePathRule]::FromXml($_) | |
} catch { | |
Write-Warning -Message "Failed to translate $($o) to Applocker FilePathRule because $($_.Exception.Message)" | |
} | |
break | |
} | |
'^<FilePublisherRule' { | |
try { | |
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FilePublisherRule]::FromXml($_) | |
} catch { | |
Write-Warning -Message "Failed to translate $($o) to Applocker FilePublisherRule because $($_.Exception.Message)" | |
} | |
break | |
} | |
'^<FileHashRule' { | |
try { | |
[Microsoft.Security.ApplicationId.PolicyManagement.PolicyModel.FileHashRule]::FromXml($_) | |
} catch { | |
Write-Warning -Message "Failed to translate $($o) to Applocker FilePublisherRule because $($_.Exception.Message)" | |
} | |
break | |
} | |
default { | |
Write-Warning -Message 'Failed to transtale input into an Applocker rule' | |
} | |
} | |
} | |
} | |
End {} | |
} | |
#endregion | |
Function Get-ApplockerPolicyRuleScore { | |
<# | |
.SYNOPSIS | |
Score an Applocker policy rule | |
.DESCRIPTION | |
Score an Applocker policy rule | |
.PARAMETER Type | |
Parameter that indicates the RuleCollectionType to find a rule | |
.PARAMETER Rule | |
Dynamic parameter that indicates the Name of the rule to score | |
.EXAMPLE | |
Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts' | |
Id Name Score XML | |
-- ---- ----- --- | |
ed97d0cb-15ff-430f-b82c-8d7832957725 (Default Rule) All scripts 1210 <FilePathRule Id | |
.EXAMPLE | |
$rule = Get-ApplockerPolicyRuleScore -Type Script -Rule '(Default Rule) All scripts' -Verbose | |
Write-Verbose -Message "Final score for '$($rule.Name)' is $($rule.Score)" -Verbose | |
$rule.Object | |
VERBOSE: Dealing with Rule Collection type: Script | |
VERBOSE: Dealing with Rule Name: (Default Rule) All scripts | |
VERBOSE: Applocker score: 100 ; Path type | |
VERBOSE: Applocker score: *1 ; Action is Allow | |
VERBOSE: Applocker score: 10 ; Administrators | |
VERBOSE: Applocker score: Path type | |
VERBOSE: Depth score is 100 | |
VERBOSE: Path score: 1000 (only a *) | |
VERBOSE: Final score for '(Default Rule) All scripts' is 1210 | |
PathConditions : {*} | |
PathExceptions : {} | |
PublisherExceptions : {} | |
HashExceptions : {} | |
Id : ed97d0cb-15ff-430f-b82c-8d7832957725 | |
Name : (Default Rule) All scripts | |
Description : Allows members of the local Administrators group to run all scripts. | |
UserOrGroupSid : S-1-5-32-544 | |
Action : Allow | |
#> | |
[CmdletBinding()] | |
Param( | |
[ValidateSet('Exe','Script','Msi','Appx','Dll')] | |
[Parameter(Mandatory)] | |
[String]$Type | |
) | |
DynamicParam { | |
$Dictionary = New-Object -TypeName System.Management.Automation.RuntimeDefinedParameterDictionary | |
#region helper function | |
Function New-ParameterAttributCollection { | |
[CmdletBinding(SupportsShouldProcess)] | |
Param( | |
[Switch]$Mandatory, | |
[Switch]$ValueFromPipeline, | |
[Switch]$ValueFromPipelineByPropertyName, | |
[String]$ParameterSetName, | |
[Parameter()] | |
[ValidateSet( | |
'Arguments','Count','Drive','EnumeratedArguments','Length','NotNull', | |
'NotNullOrEmpty','Pattern','Range','Script','Set','UserDrive' | |
)][string]$ValidateType, | |
[Parameter()] | |
$ValidationContent | |
) | |
Begin {} | |
Process { | |
if ($PSCmdlet.ShouldProcess('Create new Attribute')) { | |
$c = New-Object System.Collections.ObjectModel.Collection[System.Attribute] | |
$a = New-Object System.Management.Automation.ParameterAttribute | |
if ($Mandatory) { | |
$a.Mandatory = $true | |
} | |
if ($ValueFromPipeline) { | |
$a.ValueFromPipeline = $true | |
} | |
if ($ValueFromPipelineByPropertyName) { | |
$a.ValueFromPipelineByPropertyName=$true | |
} | |
if ($ParameterSetName) { | |
$a.ParameterSetName = $ParameterSetName | |
} | |
$c.Add($a) | |
if ($ValidateType -and $ValidationContent) { | |
try { | |
$c.Add((New-Object "System.Management.Automation.Validate$($ValidateType)Attribute"( | |
$ValidationContent | |
))) | |
} catch { | |
Throw $_ | |
} | |
} | |
$c | |
} | |
} | |
End {} | |
} | |
#endregion | |
try { | |
$ApplockerPolicyXml = [xml](Get-AppLockerPolicy -Effective -Xml -ErrorAction Stop) | |
} catch { | |
Throw 'Failed to read the effective Applocker policy into XML' | |
} | |
#region param Rule | |
$Dictionary.Add( | |
'Rule', | |
(New-Object System.Management.Automation.RuntimeDefinedParameter( | |
'Rule', | |
[string], | |
(New-ParameterAttributCollection -Mandatory -ValidateType Set -ValidationContent ( | |
$ApplockerPolicyXml.SelectNodes("/AppLockerPolicy/RuleCollection[@Type='$($PSBoundParameters['Type'])']").ChildNodes| ForEach-Object { $_.Name } | |
)) | |
)) | |
) | |
$Dictionary | |
} | |
Begin {} | |
Process { | |
Write-Verbose -Message "Dealing with Rule Collection type: $($PSBoundParameters['Type'])" | |
Write-Verbose -Message "Dealing with Rule Name: $($PSBoundParameters['Rule'])" | |
# Select node | |
$n = $ApplockerPolicyXml.SelectNodes("/AppLockerPolicy/RuleCollection[@Type='$($PSBoundParameters['Type'])']").ChildNodes | | |
Where-Object { $_.Name -eq "$($PSBoundParameters['Rule'])" } | |
try { | |
$TypeScore = Get-ApplockerRuleTypeScore -InputObject $n.OuterXml | |
$ActionSore = Get-ApplockerRuleActionScore -InputObject $n.OuterXml | |
$IdScore = Get-ApplockerRuleUserOrGroupSidScore -InputObject $n.OuterXml | |
$ConditionScore = Get-ApplockerRuleConditionsScore -InputObject $n.OuterXml | |
$r = [PSCustomObject]@{ | |
Id = $n.Id | |
Name = $n.Name | |
Score = ($TypeScore+$IdScore+$ConditionScore)*$ActionSore | |
XML = $n.OuterXml | |
Object = Get-ApplockerRuleObjectFromXml -InputObject $n.OuterXml | |
} | |
Update-TypeData -TypeName 'Applocker.Rule.Score' -DefaultDisplayPropertySet 'Id','Name','Score','XML' -Force | |
$r.PSTypeNames.Insert(0,'Applocker.Rule.Score') | |
$r | |
} catch { | |
Throw "Something went wrong while scoring applocker rule: $($_.Exception.Message)" | |
} | |
} | |
End {} | |
} # endof Get-ApplockerPolicyRuleScore | |
Export-ModuleMember -Function 'Get-ApplockerPolicyRuleScore' |