File size: 6,155 Bytes
064bfd6 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 | /**
* Shared constants for PowerShell cmdlets that execute arbitrary code.
*
* These lists are consumed by both the permission-engine validators
* (powershellSecurity.ts) and the UI suggestion gate (staticPrefix.ts).
* Keeping them here avoids duplicating the lists and prevents sync drift
* β add a cmdlet once, both consumers pick it up.
*/
import { CROSS_PLATFORM_CODE_EXEC } from '../permissions/dangerousPatterns.js'
import { COMMON_ALIASES } from './parser.js'
/**
* Cmdlets that accept a -FilePath (or positional path) and execute the
* file's contents as a script.
*/
export const FILEPATH_EXECUTION_CMDLETS = new Set([
'invoke-command',
'start-job',
'start-threadjob',
'register-scheduledjob',
])
/**
* Cmdlets where a scriptblock argument executes arbitrary code (not just
* filtering/transforming pipeline input like Where-Object).
*/
export const DANGEROUS_SCRIPT_BLOCK_CMDLETS = new Set([
'invoke-command',
'invoke-expression',
'start-job',
'start-threadjob',
'register-scheduledjob',
'register-engineevent',
'register-objectevent',
'register-wmievent',
'new-pssession',
'enter-pssession',
])
/**
* Cmdlets that load and execute module/script code. `.psm1` files run
* their top-level body on import β same code-execution risk as iex.
*/
export const MODULE_LOADING_CMDLETS = new Set([
'import-module',
'ipmo',
'install-module',
'save-module',
'update-module',
'install-script',
'save-script',
])
/**
* Shells and process spawners. Small, stable β add here only for cmdlets
* not covered by the validator lists above.
*/
const SHELLS_AND_SPAWNERS = [
'pwsh',
'powershell',
'cmd',
'bash',
'wsl',
'sh',
'start-process',
'start',
'add-type',
'new-object',
] as const
function aliasesOf(targets: ReadonlySet<string>): string[] {
return Object.entries(COMMON_ALIASES)
.filter(([, target]) => targets.has(target.toLowerCase()))
.map(([alias]) => alias)
}
/**
* Network cmdlets β wildcard rules for these enable exfil/download without
* prompt. No legitimate narrow prefix exists.
*/
export const NETWORK_CMDLETS = new Set([
'invoke-webrequest',
'invoke-restmethod',
])
/**
* Alias/variable mutation cmdlets β Set-Alias rebinds command resolution,
* Set-Variable can poison $PSDefaultParameterValues. checkRuntimeStateManipulation
* validator in powershellSecurity.ts independently gates on the permission path.
*/
export const ALIAS_HIJACK_CMDLETS = new Set([
'set-alias',
'sal', // alias not in COMMON_ALIASES β list explicitly
'new-alias',
'nal', // alias not in COMMON_ALIASES β list explicitly
'set-variable',
'sv', // alias not in COMMON_ALIASES β list explicitly
'new-variable',
'nv', // alias not in COMMON_ALIASES β list explicitly
])
/**
* WMI/CIM process spawn β Invoke-WmiMethod -Class Win32_Process -Name Create
* is a Start-Process equivalent that bypasses checkStartProcess. No legitimate
* narrow prefix exists; any invocation can spawn arbitrary processes.
* checkWmiProcessSpawn validator gates on the permission path.
* (security finding #34)
*/
export const WMI_CIM_CMDLETS = new Set([
'invoke-wmimethod',
'iwmi', // alias not in COMMON_ALIASES β list explicitly
'invoke-cimmethod',
])
/**
* Cmdlets in CMDLET_ALLOWLIST with additionalCommandIsDangerousCallback.
*
* The allowlist auto-allows these for safe args (StringConstant identifiers).
* The permission dialog only fires when the callback rejected β i.e. the args
* contain a scriptblock, variable, subexpression, etc. Accepting a
* `Cmdlet:*` wildcard at that point would match ALL future invocations via
* prefix-startsWith, bypassing the callback forever.
* `ForEach-Object:*` β `ForEach-Object { Remove-Item -Recurse / }` auto-allows.
*
* Sync with readOnlyValidation.ts β test/utils/powershell/dangerousCmdlets.test.ts
* asserts this set covers every additionalCommandIsDangerousCallback entry.
*/
export const ARG_GATED_CMDLETS = new Set([
'select-object',
'sort-object',
'group-object',
'where-object',
'measure-object',
'write-output',
'write-host',
'start-sleep',
'format-table',
'format-list',
'format-wide',
'format-custom',
'out-string',
'out-host',
// Native executables with callback-gated args (e.g. ipconfig /flushdns
// is rejected, ipconfig /all is allowed). Same bypass risk.
'ipconfig',
'hostname',
'route',
])
/**
* Commands to never suggest as a wildcard prefix in the permission dialog.
*
* Derived from the validator lists above plus the small static shells list.
* Add a cmdlet to the appropriate validator list and it automatically
* appears here β no separate maintenance.
*/
export const NEVER_SUGGEST: ReadonlySet<string> = (() => {
const core = new Set<string>([
...SHELLS_AND_SPAWNERS,
...FILEPATH_EXECUTION_CMDLETS,
...DANGEROUS_SCRIPT_BLOCK_CMDLETS,
...MODULE_LOADING_CMDLETS,
...NETWORK_CMDLETS,
...ALIAS_HIJACK_CMDLETS,
...WMI_CIM_CMDLETS,
...ARG_GATED_CMDLETS,
// ForEach-Object's -MemberName (positional: `% Delete`) resolves against
// the runtime pipeline object β `Get-ChildItem | % Delete` invokes
// FileInfo.Delete(). StaticParameterBinder identifies the
// PropertyAndMethodSet parameter set, but the set handles both; the arg
// is a plain StringConstantExpressionAst with no property/method signal.
// Pipeline type inference (upstream OutputType β GetMember) misses ETS
// AliasProperty members and has no answer for `$var | %` or external
// upstream. Not in ARG_GATED (no allowlist entry to sync with).
'foreach-object',
// Interpreters/runners β `node script.js` stops at the file arg and
// suggests bare `node:*`, auto-allowing arbitrary code via -e/-p. The
// auto-mode classifier strips these rules (isDangerousPowerShellPermission)
// but the suggestion gate didn't. Multi-word entries ('npm run') are
// filtered out β NEVER_SUGGEST is a single-name lookup on cmd.name.
...CROSS_PLATFORM_CODE_EXEC.filter(p => !p.includes(' ')),
])
return new Set([...core, ...aliasesOf(core)])
})()
|