212 lines
7.1 KiB
TypeScript
212 lines
7.1 KiB
TypeScript
/**
|
|
* PowerShell Constrained Language Mode allowed types.
|
|
*
|
|
* Microsoft's CLM restricts .NET type usage to this allowlist when PS runs
|
|
* under AppLocker/WDAC system lockdown. Any type NOT in this set is considered
|
|
* unsafe for untrusted code execution.
|
|
*
|
|
* We invert this: type literals not in this set → ask. One canonical check
|
|
* replaces enumerating individual dangerous types (named pipes, reflection,
|
|
* process spawning, P/Invoke marshaling, etc.). Microsoft maintains the list.
|
|
*
|
|
* Source: https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_language_modes
|
|
*
|
|
* Normalization: entries stored lowercase, short AND full names where both
|
|
* exist (PS resolves type accelerators like [int] → System.Int32 at runtime;
|
|
* we match against what the AST emits, which is the literal text).
|
|
*/
|
|
export const CLM_ALLOWED_TYPES: ReadonlySet<string> = new Set(
|
|
[
|
|
// Type accelerators (short names as they appear in AST TypeName.Name)
|
|
// SECURITY: 'adsi' and 'adsisearcher' REMOVED. Both are Active Directory
|
|
// Service Interface types that perform NETWORK BINDS when cast:
|
|
// [adsi]'LDAP://evil.com/...' → connects to LDAP server
|
|
// [adsisearcher]'(objectClass=user)' → binds to AD and queries
|
|
// Microsoft's CLM allows these because it's for Windows admins in trusted
|
|
// domains; we block them since the target isn't validated.
|
|
'alias',
|
|
'allowemptycollection',
|
|
'allowemptystring',
|
|
'allownull',
|
|
'argumentcompleter',
|
|
'argumentcompletions',
|
|
'array',
|
|
'bigint',
|
|
'bool',
|
|
'byte',
|
|
'char',
|
|
'cimclass',
|
|
'cimconverter',
|
|
'ciminstance',
|
|
// 'cimsession' REMOVED — see wmi/adsi comment below
|
|
'cimtype',
|
|
'cmdletbinding',
|
|
'cultureinfo',
|
|
'datetime',
|
|
'decimal',
|
|
'double',
|
|
'dsclocalconfigurationmanager',
|
|
'dscproperty',
|
|
'dscresource',
|
|
'experimentaction',
|
|
'experimental',
|
|
'experimentalfeature',
|
|
'float',
|
|
'guid',
|
|
'hashtable',
|
|
'int',
|
|
'int16',
|
|
'int32',
|
|
'int64',
|
|
'ipaddress',
|
|
'ipendpoint',
|
|
'long',
|
|
'mailaddress',
|
|
'norunspaceaffinity',
|
|
'nullstring',
|
|
'objectsecurity',
|
|
'ordered',
|
|
'outputtype',
|
|
'parameter',
|
|
'physicaladdress',
|
|
'pscredential',
|
|
'pscustomobject',
|
|
'psdefaultvalue',
|
|
'pslistmodifier',
|
|
'psobject',
|
|
'psprimitivedictionary',
|
|
'pstypenameattribute',
|
|
'ref',
|
|
'regex',
|
|
'sbyte',
|
|
'securestring',
|
|
'semver',
|
|
'short',
|
|
'single',
|
|
'string',
|
|
'supportswildcards',
|
|
'switch',
|
|
'timespan',
|
|
'uint',
|
|
'uint16',
|
|
'uint32',
|
|
'uint64',
|
|
'ulong',
|
|
'uri',
|
|
'ushort',
|
|
'validatecount',
|
|
'validatedrive',
|
|
'validatelength',
|
|
'validatenotnull',
|
|
'validatenotnullorempty',
|
|
'validatenotnullorwhitespace',
|
|
'validatepattern',
|
|
'validaterange',
|
|
'validatescript',
|
|
'validateset',
|
|
'validatetrusteddata',
|
|
'validateuserdrive',
|
|
'version',
|
|
'void',
|
|
'wildcardpattern',
|
|
// SECURITY: 'wmi', 'wmiclass', 'wmisearcher', 'cimsession' REMOVED.
|
|
// WMI type casts perform WMI queries which can target remote computers
|
|
// (network request) and access dangerous classes like Win32_Process.
|
|
// cimsession creates a CIM session (network connection to remote host).
|
|
// [wmi]'\\evil-host\root\cimv2:Win32_Process.Handle="1"' → remote WMI
|
|
// [wmisearcher]'SELECT * FROM Win32_Process' → runs WQL query
|
|
// Same rationale as adsi/adsisearcher removal above.
|
|
'x500distinguishedname',
|
|
'x509certificate',
|
|
'xml',
|
|
// Full names for accelerators that resolve to System.* (AST may emit either)
|
|
'system.array',
|
|
'system.boolean',
|
|
'system.byte',
|
|
'system.char',
|
|
'system.datetime',
|
|
'system.decimal',
|
|
'system.double',
|
|
'system.guid',
|
|
'system.int16',
|
|
'system.int32',
|
|
'system.int64',
|
|
'system.numerics.biginteger',
|
|
'system.sbyte',
|
|
'system.single',
|
|
'system.string',
|
|
'system.timespan',
|
|
'system.uint16',
|
|
'system.uint32',
|
|
'system.uint64',
|
|
'system.uri',
|
|
'system.version',
|
|
'system.void',
|
|
'system.collections.hashtable',
|
|
'system.text.regularexpressions.regex',
|
|
'system.globalization.cultureinfo',
|
|
'system.net.ipaddress',
|
|
'system.net.ipendpoint',
|
|
'system.net.mail.mailaddress',
|
|
'system.net.networkinformation.physicaladdress',
|
|
'system.security.securestring',
|
|
'system.security.cryptography.x509certificates.x509certificate',
|
|
'system.security.cryptography.x509certificates.x500distinguishedname',
|
|
'system.xml.xmldocument',
|
|
// System.Management.Automation.* — FQ equivalents of PS-specific accelerators
|
|
'system.management.automation.pscredential',
|
|
'system.management.automation.pscustomobject',
|
|
'system.management.automation.pslistmodifier',
|
|
'system.management.automation.psobject',
|
|
'system.management.automation.psprimitivedictionary',
|
|
'system.management.automation.psreference',
|
|
'system.management.automation.semanticversion',
|
|
'system.management.automation.switchparameter',
|
|
'system.management.automation.wildcardpattern',
|
|
'system.management.automation.language.nullstring',
|
|
// Microsoft.Management.Infrastructure.* — FQ equivalents of CIM accelerators
|
|
// SECURITY: cimsession FQ REMOVED — same network-bind hazard as short name
|
|
// (creates a CIM session to a remote host).
|
|
'microsoft.management.infrastructure.cimclass',
|
|
'microsoft.management.infrastructure.cimconverter',
|
|
'microsoft.management.infrastructure.ciminstance',
|
|
'microsoft.management.infrastructure.cimtype',
|
|
// FQ equivalents of remaining short-name accelerators
|
|
// SECURITY: DirectoryEntry/DirectorySearcher/ManagementObject/
|
|
// ManagementClass/ManagementObjectSearcher FQ REMOVED — same network-bind
|
|
// hazard as short names adsi/adsisearcher/wmi/wmiclass/wmisearcher
|
|
// (LDAP bind, remote WMI). See short-name removal comments above.
|
|
'system.collections.specialized.ordereddictionary',
|
|
'system.security.accesscontrol.objectsecurity',
|
|
// Arrays of allowed types are allowed (e.g. [string[]])
|
|
// normalizeTypeName strips [] before lookup, so store the base name
|
|
'object',
|
|
'system.object',
|
|
// ModuleSpecification — full qualified name
|
|
'microsoft.powershell.commands.modulespecification',
|
|
].map(t => t.toLowerCase()),
|
|
)
|
|
|
|
/**
|
|
* Normalize a type name from AST TypeName.FullName or TypeName.Name.
|
|
* Handles array suffix ([]) and generic brackets.
|
|
*/
|
|
export function normalizeTypeName(name: string): string {
|
|
// Strip array suffix: "String[]" → "string" (arrays of allowed types are allowed)
|
|
// Strip generic args: "List[int]" → "list" (conservative — the generic wrapper
|
|
// might be unsafe even if the type arg is safe, so we check the outer type)
|
|
return name
|
|
.toLowerCase()
|
|
.replace(/\[\]$/, '')
|
|
.replace(/\[.*\]$/, '')
|
|
.trim()
|
|
}
|
|
|
|
/**
|
|
* True if typeName (from AST) is in Microsoft's CLM allowlist.
|
|
* Types NOT in this set trigger ask — they access system APIs CLM blocks.
|
|
*/
|
|
export function isClmAllowedType(typeName: string): boolean {
|
|
return CLM_ALLOWED_TYPES.has(normalizeTypeName(typeName))
|
|
}
|