Skip to content

Instantly share code, notes, and snippets.

@FriedrichWeinmann
Last active November 17, 2021 18:18
Show Gist options
  • Save FriedrichWeinmann/06fe1d9dfeddb186d1aa73577616bb4f to your computer and use it in GitHub Desktop.
Save FriedrichWeinmann/06fe1d9dfeddb186d1aa73577616bb4f to your computer and use it in GitHub Desktop.
Tests, whether the targeted file would have trouble executing under Constrained Language Mode.
function Test-ClmCompatibility {
<#
.SYNOPSIS
Tests, whether the targeted file would have trouble executing under Constrained Language Mode.
.DESCRIPTION
Tests, whether the targeted file would have trouble executing under Constrained Language Mode (CLM).
In CLM, various language features and commands are constrained in their ability to execute.
This command uses the AST parser to scan for as many known issues as possible and gives a comprehensive report for concerns found.
Detected Issues:
- Custom Object creation using PSCustomObject
- Calling methods on untrusted types
- Converting to an untrusted type
- Using Add-Type to load anything but trusted libraries
- Using New-Object to instantiate an untrusted type
- Assigning Values to properties*
*This detection will likely have a large rate of false positives, due to inability to detect datatype of the object, the property of which is being set.
Generally, assigning values to the properties of PSObjects is fine.
Note:
Many of the detections make allowances for "whitelisted types".
In CLM, access to most types is constrained, except for a few, known to be trustworthy types.
To get a full list of the constraints and what types are allowed, see the documentation:
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_modes?view=powershell-7.1#constrained-language-constrained-language
.PARAMETER Path
Path to the scriptfile to scan.
.EXAMPLE
PS C:\> Get-ChildItem C:\Scripts | Test-ClmCompatibility
Scans each file in C:\Scripts and returns any issues that might occur in CLM.
.LINK
https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_modes?view=powershell-7.1#constrained-language-constrained-language
#>
[CmdletBinding()]
param (
[Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
[Alias('FullName')]
[string[]]
$Path
)
begin {
#region Safe Types
$safeTypes = @(
[System.Array]
[System.Boolean]
[System.Byte]
[System.Char]
[System.DateTime]
[System.Decimal]
[System.Double]
[System.Single]
[System.Guid]
[System.Collections.Hashtable]
[System.Int32]
[System.Int16]
[System.Int64]
[System.Management.Automation.Language.NullString]
[System.Management.Automation.PSCredential]
[System.Management.Automation.PSListModifier]
[System.Management.Automation.PSObject]
[System.Management.Automation.PSPrimitiveDictionary]
[System.Management.Automation.PSTypeNameAttribute]
[System.Text.RegularExpressions.Regex]
[System.SByte]
[System.String]
[System.Globalization.CultureInfo]
[System.Net.IPAddress]
[System.Net.Mail.MailAddress]
[System.Numerics.BigInteger]
[System.Security.SecureString]
[System.TimeSpan]
[System.UInt16]
[System.UInt32]
[System.UInt64]
[System.Management.Automation.AliasAttribute]
[System.Management.Automation.AllowEmptyCollectionAttribute]
[System.Management.Automation.AllowEmptyStringAttribute]
[System.Management.Automation.AllowNullAttribute]
[System.Management.Automation.CmdletBindingAttribute]
[System.DirectoryServices.DirectoryEntry]
[System.DirectoryServices.DirectorySearcher]
[System.Management.ManagementClass]
[System.Management.ManagementObject]
[System.Management.ManagementObjectSearcher]
[System.Management.Automation.OutputTypeAttribute]
[System.Management.Automation.ParameterAttribute]
[System.Management.Automation.PSDefaultValueAttribute]
[System.Management.Automation.PSReference]
[System.Management.Automation.SupportsWildcardsAttribute]
[System.Management.Automation.SwitchParameter]
)
#endregion Safe Types
#region Utility Functions
function Search-Ast {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
[ScriptBlock]
$Filter,
[string]
$Type,
[string]
$Explanation
)
$results = $Ast.FindAll($Filter, $true)
foreach ($result in $results) {
[PSCustomObject]@{
Type = $Type
Line = $result.Extent.StartLineNumber
File = $Ast.Extent.File
Data = $result
Explanation = $Explanation
}
}
}
function Format-Result {
[CmdletBinding()]
param (
[Parameter(ValueFromPipeline = $true)]
$Result
)
begin {
$defaultDisplaySet = 'Type', 'Line', 'File', 'Data'
$defaultDisplayPropertySet = New-Object System.Management.Automation.PSPropertySet(‘DefaultDisplayPropertySet’, [string[]]$defaultDisplaySet)
$standardMembers = [System.Management.Automation.PSMemberInfo[]]@($defaultDisplayPropertySet)
}
process {
$Result | Add-Member -MemberType MemberSet -Name PSStandardMembers -Value $standardMembers -PassThru
}
}
function Find-PSCustomObject {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'Custom object creation using PSCustomObject is not available in CLM. You can work around this issue by replacing it with "New-Object PSObject -Properties @{ ... }", which works in CLM.'
Search-Ast -Ast $Ast -Type PSCustomObject -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.ConvertExpressionAst]) { return }
if ($args[0].Type.TypeName.Name -ne 'PSCustomObject') { return }
$true
} | Format-Result
}
function Find-MethodInvocation {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'Cannot call methods on objects in CLM, other than ToString, unless the type is one of the few basic trusted types such as string or integer'
Search-Ast -Ast $Ast -Type 'Method Invocation' -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.InvokeMemberExpressionAst]) { return }
if ($args[0].Expression.StaticType -in $SafeTypes) { return }
if ($args[0].Member.Value -eq 'ToString') { return }
$true
} | Format-Result
}
function Find-TypeConversion {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'Cannot convert to types not trusted in CLM. Trusted types are few, including very simple types such as string or integer.'
Search-Ast -Ast $Ast -Type 'Type Conversion' -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.ConvertExpressionAst]) { return }
if ($args[0].StaticType -in $SafeTypes) { return }
if ($args[0].Type.TypeName.Name -eq 'PSCustomObject' -and $args[0].Child.StaticType -eq [hashtable]) { return }
$true
} | Format-Result
}
function Find-AddType {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'Add-Type can only load signed and trusted libraries. This includes core .NET assemblies loaded by name. If loading an assembly from file, this detection will trigger, as it does not verify the file referenced. If the targeted dll is signed and trusted, disregard this detection.'
Search-Ast -Ast $Ast -Type 'Add-Type' -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.CommandAst]) { return }
if ($args[0].CommandElements[0].Value -ne 'Add-Type') { return }
if ($args[0].CommandElements.ParameterName -contains 'AssemblyName') { return }
$true
} | Format-Result
}
function Find-NewObject {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'New-Object cannot be used in CLM, except to create an object of one of a set of explicitly whitelisted types, such as strings, integers, DateTime, etc.'
Search-Ast -Ast $Ast -Type 'New-Object' -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.CommandAst]) { return }
if ($args[0].CommandElements[0].Value -ne 'New-Object') { return }
if ($args[0].CommandElements | Where-Object Value -In $SafeTypes.FullName) { return }
if ($args[0].CommandElements | Where-Object Value -eq 'PSObject') { return }
$true
} | Format-Result
}
function Find-PropertyAssignment {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'Under CLM, assigning values to properties doesn''t work, unless the type is explicitly whitelisted by the engine. Generic PSObject objects - such as returned by ConvertFrom-Json or Import-Csv - ARE whitelisted however, so this scan may have a few false positives, sorry.'
Search-Ast -Ast $Ast -Type 'Property Assignment' -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.AssignmentStatementAst]) { return }
if ($args[0].Left -isnot [System.Management.Automation.Language.MemberExpressionAst]) { return }
if ($args[0].CommandElements | Where-Object Value -In $SafeTypes.FullName) { return }
$true
} | Format-Result
}
function Find-ClassDefinition {
[CmdletBinding()]
param (
[System.Management.Automation.Language.Ast]
$Ast,
$SafeTypes
)
$explanation = 'PowerShell classes are not supported in Constrained Language Mode.'
Search-Ast -Ast $Ast -Type 'PowerShell Class' -Explanation $explanation -Filter {
if ($args[0] -isnot [System.Management.Automation.Language.TypeDefinitionAst]) { return }
if ($args[0].TypeAttributes -eq 'Enum') { return }
$true
} | Format-Result
}
#endregion Utility Functions
}
process {
foreach ($file in ($Path | Resolve-Path).Path) {
try { $ast = [System.Management.Automation.Language.Parser]::ParseFile($file, [ref]$null, [ref]$null) }
catch {
Write-Warning "Error parsing: $file | $_"
Write-Error $_
continue
}
$param = @{
Ast = $ast
SafeTypes = $safeTypes
}
Find-PSCustomObject @param
Find-MethodInvocation @param
Find-TypeConversion @param
Find-AddType @param
Find-NewObject @param
Find-PropertyAssignment @param
Find-ClassDefinition @param
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment