PowerShell 脚本和其他编程语言一样,可能容易受到注入攻击。 当用户向包含额外命令的易受攻击函数提供输入时,将发生注入攻击。 易受攻击的函数运行额外的命令,这可以是严重的安全漏洞。 例如,恶意用户可能会滥用易受攻击的函数在远程计算机上运行任意代码,这可能会破坏该计算机并获取对网络上其他计算机的访问。
了解问题后,可通过多种方法来防范注入攻击。
易受攻击的代码示例
PowerShell 代码注入漏洞涉及包含脚本代码的用户输入。 用户输入将添加到易受攻击的脚本中,该脚本由 PowerShell 分析并运行。
function Get-ProcessById
{
param ($ProcId)
Invoke-Expression -Command "Get-Process -Id $ProcId"
}
该 Get-ProcessById
函数按其 ID 值查找本地进程。 它采用 $ProcId
任何类型的参数参数。 然后,该 $ProcId
字符串转换为字符串,并插入到使用 Invoke-Expression
cmdlet 分析和运行的另一个脚本中。 传入有效的进程 ID 整数时,此函数可以正常工作。
Get-ProcessById $PID
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
97 50.09 132.72 1.20 12528 3 pwsh
但是,该 $ProcId
参数未指定类型。 它接受可以包含其他命令的任何任意字符串值。
Get-ProcessById "$PID; Write-Host 'pwnd!'"
在此示例中,函数正确检索了由 $PID
它标识的进程,但也运行了注入的脚本 Write-Host 'pwnd!'
。
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
92 45.66 122.52 1.06 21736 3 pwsh
pwnd!
防范注入攻击的方法
这是几种防止注射攻击的方法。
使用类型化输入
可以指定 $ProcId
参数的类型。
function Get-ProcessById
{
param ([int] $ProcId)
Invoke-Expression -Command "Get-Process -Id $ProcId"
}
Get-ProcessById "$PID; Write-Host 'pwnd!'"
Get-ProcessById:
Line |
7 | Get-ProcessById "$PID; Write-Host 'pwnd!'"
| ~~~~~~~~~~~~~~~~~~~~~~~~~
| Cannot process argument transformation on parameter 'ProcId'. Cannot convert value
"8064; Write-Host 'pwnd!'" to type "System.Int32". Error: "The input string '8064; Write-Host 'pwnd!'
was not in a correct format."
此处, $ProcId
输入参数仅限于整数类型,因此当传入的字符串无法转换为整数时,将发生错误。
不要使用 Invoke-Expression
与其使用 Invoke-Expression
,不如直接调用 Get-Process
,并让 PowerShell 的参数绑定器验证输入。
function Get-ProcessById
{
param ($ProcId)
Get-Process -Id $ProcId
}
Get-ProcessById "$PID; Write-Host 'pwnd!'"
Get-Process:
Line |
5 | Get-Process -Id $ProcId
| ~~~~~~~
| Cannot bind parameter 'Id'. Cannot convert value "8064; Write-Host 'pwnd!'" to type
"System.Int32". Error: "The input string '8064; Write-Host 'pwnd!' was not in a correct
format."
最佳做法是应避免使用 Invoke-Expression
,尤其是在处理用户输入时。
Invoke-Expression
很危险,因为它分析并运行你提供的任何字符串内容,使其容易受到注入攻击。 最好依赖于 PowerShell 参数绑定。
用单引号包装字符串
但是,有时使用 Invoke-Expression
是不可避免的,你还需要处理用户字符串输入。 可以在每个字符串输入变量周围使用单引号安全地处理用户输入。 单引号可确保 PowerShell 分析器将用户输入视为单个字符串文本。
function Get-ProcessById
{
param ($ProcId)
Invoke-Expression -Command "Get-Process -Id '$ProcId'"
}
Get-ProcessById "$PID; Write-Host 'pwnd!'"
Get-Process: Cannot bind parameter 'Id'. Cannot convert value "8064; Write-Host " to type
"System.Int32". Error: "The input string '8064; Write-Host' was not in a correct format."
但是,此版本的函数尚未完全安全免受注入攻击。 恶意用户仍然可以在其输入中使用单引号来注入代码。
Get-ProcessById "$PID'; Write-Host 'pwnd!';'"
此示例使用用户输入中的单引号来强制函数运行三个单独的语句,其中一个语句是用户注入的任意代码。
NPM(K) PM(M) WS(M) CPU(s) Id SI ProcessName
------ ----- ----- ------ -- -- -----------
97 46.08 183.10 1.08 2524 3 pwsh
pwnd!
使用EscapeSingleQuotedStringContent()
方法
若要防止用户插入自己的单引号字符来利用函数,必须使用 EscapeSingleQuotedStringContent()
API。 这是 PowerShell System.Management.Automation.Language.CodeGeneration 类的静态公共方法。 此方法通过转义用户输入中的单引号,从而确保用户输入的安全。
function Get-ProcessById
{
param ($ProcId)
$ProcIdClean = [System.Management.Automation.Language.CodeGeneration]::
EscapeSingleQuotedStringContent("$ProcId")
Invoke-Expression -Command "Get-Process -Id '$ProcIdClean'"
}
Get-ProcessById "$PID'; Write-Host 'pwnd!';'"
Get-Process: Cannot bind parameter 'Id'. Cannot convert value "8064'; Write-Host 'pwnd!';'" to type
"System.Int32". Error: "The input string '8064'; Write-Host 'pwnd!';'' was not in a correct format."
有关详细信息,请参阅 EscapeSingleQuotedStringContent()。
使用注入猎人检测易受攻击的代码
注入猎人 是由 Lee Holmes 编写的模块,其中包含用于检测代码注入漏洞的 PowerShell 脚本分析器规则。 使用以下命令之一从 PowerShell 库安装模块:
# Use PowerShellGet v2.x
Install-Module InjectionHunter
# Use PowerShellGet v3.x
Install-PSResource InjectionHunter
可以使用此功能在生成、持续集成过程、部署和其他方案中自动执行安全分析。
$RulePath = (Get-Module -List InjectionHunter).Path
Invoke-ScriptAnalyzer -CustomRulePath $RulePath -Path .\Invoke-Dangerous.ps1
RuleName Severity ScriptName Line Message
-------- -------- ---------- ---- -------
InjectionRisk.InvokeExpression Warning Invoke-Dan 3 Possible script injection risk via the
gerous.ps1 Invoke-Expression cmdlet. Untrusted input can cause
arbitrary PowerShell expressions to be run.
Variables may be used directly for dynamic parameter
arguments, splatting can be used for dynamic
parameter names, and the invocation operator can be
used for dynamic command names. If content escaping
is truly needed, PowerShell has several valid quote
characters, so [System.Management.Automation.Languag
e.CodeGeneration]::Escape* should be used.
有关详细信息,请参阅 PSScriptAnalyzer。