防止脚本注入攻击

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