Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use hashtable output in GetSafePathsToAllow.ps1 #7

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 73 additions & 35 deletions AaronLocker/Create-Policies.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -414,44 +414,82 @@ $csvExeBlacklistData | foreach {
# Remove the placeholder element
$xExcepts.RemoveChild($xPlaceholder) | Out-Null

Write-Host "Processing additional safe paths to whitelist..." -ForegroundColor Cyan
# Get additional whitelisted paths from the script that produces that list and incorporate them into the document
Write-Host "Processing safe paths to whitelist..." -ForegroundColor Cyan
# Get whitelisted paths from the script that produces that list and incorporate them into the document
$PathsToAllow = (& $ps1_GetSafePathsToAllow)
# Add "allow" for Everyone for Exe, Dll, and Script rules
$xRuleCollections = $xDocument.SelectNodes("//RuleCollection[@Type='Exe' or @Type='Script' or @Type='Dll']")
foreach($xRuleCollection in $xRuleCollections)
{
$PathsToAllow | foreach {
# If path is an existing directory and doesn't have trailing "\*" appended, fix it so that it does.
# If path is a file, don't append \*. If the path ends with \*, no need for further validation.
# If it doesn't end with \* but Get-Item can't identify it as a file or a directory, write a warning and accept it as is.
$pathToAllow = $_
if (!$pathToAllow.EndsWith("\*"))
{
$pathItem = Get-Item $pathToAllow -Force -ErrorAction SilentlyContinue
if ($pathItem -eq $null)
{
Write-Warning "Cannot verify path $pathItem; adding to rule set as is."
}
elseif ($pathItem -is [System.IO.DirectoryInfo])
{
Write-Warning "Appending `"\*`" to rule for $pathToAllow"
$pathToAllow = [System.IO.Path]::Combine($pathToAllow, "*")
}

# Pattern that can be replaced by %LOCALAPPDATA%
$LocalAppDataPattern = "^(%OSDRIVE%|C:)\\Users\\[^\\]*\\AppData\\Local\\"
# Pattern that can be replaced by %APPDATA%
$RoamingAppDataPattern = "^(%OSDRIVE%|C:)\\Users\\[^\\]*\\AppData\\Roaming\\"
# Pattern that can be replaced by %USERPROFILE% (after the above already done)
$UserProfilePattern = "^(%OSDRIVE%|C:)\\Users\\[^\\]*\\"
# Pattern that can be replaced by %WINDIR%
$WinDirPattern = "^(%OSDRIVE%|C:)\\Windows\\"
# Pattern that can be replaced by %SYSTEM32%
$System32Pattern = "^(%OSDRIVE%\\Windows|C:\\Windows|%WINDIR%)\\System32\\"
# Pattern that can be replaced by %PROGRAMFILES%
$ProgramFilesPattern = "^(%OSDRIVE%|C:)\\Program Files(| \(x86\))\\"
# Pattern that can be replaced by %OSDRIVE%
$OSDrivePattern = "^C:\\"

$PathsToAllow | ForEach-Object {

if (-not $_.Label) {
# Each hashtable must have a label.
Write-Error -Message ("Invalid syntax in $ps1_GetSafePathsToAllow. No `"Label`" specified.")
}
if (-not $_.Path) {
# Each hashtable must have a path
Write-Error -Message ("Invalid syntax in $ps1_GetSafePathsToAllow. No `"Path`" specified.")
}

$GenericPath = (((((($_.Path `
-ireplace $LocalAppDataPattern,"%LOCALAPPDATA%\") `
-ireplace $RoamingAppDataPattern, "%APPDATA%\") `
-ireplace $UserProfilePattern, "%USERPROFILE%\") `
-ireplace $System32Pattern, "%SYSTEM32%\") `
-ireplace $WinDirPattern, "%WINDIR%\") `
-ireplace $ProgramFilesPattern, "%PROGRAMFILES%\") `
-ireplace $OSDrivePattern, "%OSDRIVE%\"

$RulePath = (($GenericPath `
-replace "^%LOCALAPPDATA%\\","%OSDRIVE%\Users\*\AppData\Local\") `
-replace "^%APPDATA%\\","%OSDRIVE%\Users\*\AppData\Roaming\") `
-replace "^%USERPROFILE%\\","%OSDRIVE%\Users\*\"

$RuleName = "{0}: Path rule for {1}" -f $_.Label, $GenericPath
Write-Host ("`t" + $RuleName) -ForegroundColor Cyan

$elemRule = $xDocument.CreateElement("FilePathRule")
$elemRule.SetAttribute("Action", "Allow")
$elemRule.SetAttribute("UserOrGroupSid", "S-1-1-0")
$elemRule.SetAttribute("Id", [GUID]::NewGuid().Guid)
$elemRule.SetAttribute("Name", $RuleName)
$elemRule.SetAttribute("Description", "Allows Everyone to execute from " + $RulePath)
$elemConditions = $xDocument.CreateElement("Conditions")
$elemCondition = $xDocument.CreateElement("FilePathCondition")
$elemCondition.SetAttribute("Path", $RulePath)
$elemConditions.AppendChild($elemCondition) | Out-Null
$elemRule.AppendChild($elemConditions) | Out-Null

if ($_.RuleCollection) {
$CollectionNode = $xDocument.SelectSingleNode("//RuleCollection[@Type='$($_.RuleCollection)']")
if ($CollectionNode -eq $null) {
Write-Warning ("Couldn't find RuleCollection Type = " + $_.RuleCollection + " (RuleCollection is case-sensitive)")
} else {
$elemRule.Id = [string]([GUID]::NewGuid().Guid)
$CollectionNode.AppendChild($elemRule) | Out-Null
}
} else {
# Add to Exe, Dll, and Script rules
$xDocument.SelectNodes("//RuleCollection[@Type='Exe' or @Type='Script' or @Type='Dll']") | ForEach-Object {
$elemRuleCloned = $elemRule.CloneNode($true)
$elemRuleCloned.Id = [string]([GUID]::NewGuid().Guid)
$_.AppendChild($elemRuleCloned) | Out-Null
}
$elemRule = $xDocument.CreateElement("FilePathRule")
$elemRule.SetAttribute("Action", "Allow")
$elemRule.SetAttribute("UserOrGroupSid", "S-1-1-0")
$elemRule.SetAttribute("Id", [GUID]::NewGuid().Guid)
$elemRule.SetAttribute("Name", "Additional allowed path: " + $pathToAllow)
$elemRule.SetAttribute("Description", "Allows Everyone to execute from " + $pathToAllow)
$elemConditions = $xDocument.CreateElement("Conditions")
$elemCondition = $xDocument.CreateElement("FilePathCondition")
$elemCondition.SetAttribute("Path", $pathToAllow)
$elemConditions.AppendChild($elemCondition) | Out-Null
$elemRule.AppendChild($elemConditions) | Out-Null
$xRuleCollection.AppendChild($elemRule) | Out-Null
}

}

# Incorporate path-exception rules for the user-writable directories under %windir%
Expand Down
97 changes: 75 additions & 22 deletions AaronLocker/CustomizationInputs/GetSafePathsToAllow.ps1
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
<#
.SYNOPSIS
Customizable script used by Create-Policies.ps1 that produces a list of additional "safe" paths to allow for non-admin execution.
Customizable script used by Create-Policies.ps1 that produces a list of "safe"
paths to allow for non-admin execution.

.DESCRIPTION
This script outputs a simple list of directories that can be considered "safe" for non-admins to execute programs from.
The list is consumed by Create-Policies.ps1, which incorporates the paths into AppLocker rules allowing execution of
EXE, DLL, and Script files.
NOTE: DIRECTORY/FILE PATHS IDENTIFIED IN THIS SCRIPT MUST NOT BE WRITABLE BY NON-ADMIN USERS!!!
This script outputs zero or more hashtables containing information to define path rules
for files of directories that can be considered "safe" for non-admins.

The list is consumed by Create-Policies.ps1, which incorporates the paths
into AppLocker rules allowing execution of Exe, Dll, and/or Script files.

NOTE: DIRECTORY/FILE PATHS IDENTIFIED IN THIS SCRIPT SHOULD NOT BE WRITABLE
BY NON-ADMIN USERS!!!
You can edit this file as needed for your environment.

Note that each directory name must be followed by \*, as in these examples:
"C:\ProgramData\App-V\*"
"\\MYSERVER\Apps\*"
Individual files can be allowed by path, also. Do not end those with "\*"
The hashtables need to have the following required properties:
* Label: String is incorporated into the rule name and description
* Path: Path of file or directory to be whitelisted. Directories path always
need to end with "\*"", e.g.: "C:\ProgramData\App-V\*", "\\MYSERVER\Apps\*". Individual
files should not end with "\*".

The following properties are optional:
* RuleCollection: to apply the trust only within a single RuleCollection.
RuleCollection must be one of "Exe", "Dll", "Script", or "Msi", and it is CASE-SENSITIVE.

Specify paths using only fixed local drive letters or UNC paths. Do not use
mapped drive letters or SUBST drive letters, as the user can change their
definitions. If X: is mapped to the read-only \\MYSERVER\Apps file share,
and you allow execution in \\MYSERVER\Apps\*, the user can run MyProgram.exe
in that share whether it is referenced as \\MYSERVER\Apps\MyProgram.exe or
as X:\MyProgram.exe. Similarly, AppLocker does the right thing with
SUBSTed drive letters.

Paths may contain all supported AppLocker path variables for well-known
directories in Windows such as %WINDIR%, %SYSTEM32%, %OSDRIVE%, %PROGRAMFILES%,
%REMOVABLE% and %HOT%.

Specify paths using only fixed local drive letters or UNC paths. Do not use mapped drive letters or
SUBST drive letters, as the user can change their definitions. If X: is mapped to the read-only
\\MYSERVER\Apps file share, and you allow execution in \\MYSERVER\Apps\*, the user can run MyProgram.exe
in that share whether it is referenced as \\MYSERVER\Apps\MyProgram.exe or as X:\MyProgram.exe. Similarly,
AppLocker does the right thing with SUBSTed drive letters.
See also https://docs.microsoft.com/en-us/windows/security/threat-protection/windows-defender-application-control/applocker/understanding-the-path-rule-condition-in-applocker

TODO: At some point, reimplement with hashtable output supporting "label" and "RuleCollection" properties so that path rules have more descriptive names, and can be applied to specific rule collections>
Besides the known AppLocker path variables, the following special path
variables %USERPROFILE%, %LOCALAPPDATA% and %APPDATA% are also supported and
will substituted to "%OSDRIVE%\Users\*", "%OSDRIVE%\Users\*\AppData\Local"
and "%OSDRIVE%\Users\*\AppData\Roaming" resp.

Examples:

@{
Label = 'Anaconda';
Path = '%LOCALAPPDATA%\Continuum\Anaconda3\PKGS\*';
RuleCollection = 'Exe'
}

@{
Label = 'App-V';
Path = '%OSDRIVE%\App-V\*';
}

#>

Expand All @@ -35,13 +69,26 @@ if ($null -ne $cs)
if ($cs.PartOfDomain)
{
$computerDomain = $cs.Domain
"\\$computerDomain\netlogon\*"
"\\$computerDomain\sysvol\*"
@{
Label = "DC Shares";
Path = "\\$computerDomain\netlogon\*";
}
@{
Label = "DC Shares";
Path = "\\$computerDomain\sysvol\*";
}

$userDomain = $env:USERDNSDOMAIN
if ($null -ne $userDomain -and $userDomain.ToUpper() -ne $computerDomain.ToUpper())
{
"\\$userDomain\netlogon\*"
"\\$userDomain\sysvol\*"
@{
Label = "DC Shares";
Path = "\\$userDomain\netlogon\*";
}
@{
Label = "DC Shares";
Path = "\\$userDomain\sysvol\*";
}
}
}
else
Expand All @@ -51,7 +98,13 @@ if ($null -ne $cs)
}

### Windows Defender put their binaries in ProgramData for a while. Comment this back out when they move it back.
"%OSDRIVE%\PROGRAMDATA\MICROSOFT\WINDOWS DEFENDER\PLATFORM\*"
@{
Label = "Windows Defender";
Path = "%OSDRIVE%\PROGRAMDATA\MICROSOFT\WINDOWS DEFENDER\PLATFORM\*";
}

# Windows upgrade
'C:\$WINDOWS.~BT\Sources\*'
# Windows upgrade sources
@{
Label = "Windows Upgrade";
Path = '%OSDRIVE%\$WINDOWS.~BT\Sources\*';
}