如何防止在PowerShell中进行变量注入?

时间:2019-07-11 08:54:09

标签: powershell code-injection

我对@Ansgar Wiechers最近关于PowerShell的一个问题的评论再次被激发:DO NOT use Invoke-Expression关于我很久以来一直在脑海中某个地方需要提出的安全问题。 / p>

强有力的声明(参考Invoke-Expression considered harmful文章)表明,调用可以覆盖变量的脚本被认为是有害的。

另外,PSScriptAnalyzer建议不要使用Invoke-Expression,请参阅AvoidUsingInvokeExpression rule

但是我曾经使用一种技术自己在递归脚本中更新一个公共变量,该变量实际上可以覆盖其任何父范围中的值,就像这样简单:

([Ref]$ParentVariable).Value = $NewValue

据我所知,无论如何调用,在任何情况下,潜在的恶意脚本也都可以使用此技术来注入变量...

考虑以下“恶意” Inject.ps1脚本:

([Ref]$MyValue).Value = 456
([Ref]$MyString).Value = 'Injected string'
([Ref]$MyObject).Value = [PSCustomObject]@{Name = 'Injected'; Value = 'Object'}

我的Test.ps1脚本:

$MyValue = 123
$MyString = "MyString"
$MyObject = [PSCustomObject]@{Name = 'My'; Value = 'Object'}
.\Inject.ps1
Write-Host $MyValue
Write-Host $MyString
Write-Host $MyObject

结果:

456
Injected string
@{Name=Injected; Value=Object}

您看到Test.ps1范围内的所有三个变量都被Inject.ps1脚本覆盖。这也可以使用Invoke-Command cmdlet来完成,我是否将变量的范围设置为Private都没有关系:

New-Variable -Name MyValue -Value 123 -Scope Private
$MyString = "MyString"
$MyObject = [PSCustomObject]@{Name = 'My'; Value = 'Object'}
Invoke-Command {
    ([Ref]$MyValue).Value = 456
    ([Ref]$MyString).Value = 'Injected string'
    ([Ref]$MyObject).Value = [PSCustomObject]@{Name = 'Injected'; Value = 'Object'}
}
Write-Host $MyValue
Write-Host $MyString
Write-Host $MyObject

是否有一种方法可以完全隔离已调用的脚本/命令与覆盖当前作用域中的变量?
如果不是,是否可以将其视为以任何方式调用脚本的安全风险?

1 个答案:

答案 0 :(得分:2)

反对使用Invoke-Expression的建议主要是关于防止意外执行代码(代码注入)。

如果您确实有意识地调用了一段PowerShell代码,则它确实可以(可能是恶意地)操纵父范围,包括全局范围。
请注意,这种潜在的操作方式不仅限于变量:例如,还可以修改 function aliases

要提供所需的隔离,您有两个基本选择:

  • 进程中运行代码:

    • 通过启动另一个PowerShell实例;例如(在Windows PowerShell中使用powershell代替pwsh

      • pwsh { ./someUntrustedScript.ps1 }
    • 通过开始后台作业;例如:

      • Start-Job { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
  • 在单独的线程中运行代码:

    • 作为线程作业,通过ThreadJob模块(PowerShell Core 随附)以及Windows PowerShell中可以安装from the PowerShell Gallery带有类似
      Install-Module -Scope CurrentUser ThreadJob的内容;例如:

      • Start-ThreadJob { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
    • 通过PowerShell SDK创建新运行空间;例如:

      • [powershell]::Create().AddScript({ ./someUntrustedScript.ps1 }).Invoke()
      • 请注意,要获得输出流 other 而不是成功流,必须付出额外的工作,尤其是错误流的输出。同样,在命令完成后,应在PowerShell实例上调用.Dispose()

基于子进程的解决方案将很慢并且在您可以返回的数据类型方面受到限制(由于涉及序列化/反序列化),但是它提供了隔离机制,以防止所调用的代码使进程崩溃。

基于线程的作业速度更快,可以返回任何数据类型,但可能会使整个过程崩溃。

在所有情况下,您都必须将调用者需要调用的代码传递的所有值作为参数传递。


js2010提到了其他不太理想的替代方法:

  • Start-Process(基于子进程,具有纯文本参数和输出)

  • PowerShell Workflows,它们已过时(它们没有移植到PowerShell Core ,也不会移植到)。

  • 假设Invoke-Command与“回送远程处理”(-ComputerName localhost)一起使用也是一种选择,但是这样会导致子进程和基于HTTP的通信带来双重负担;另外,您必须为远程计算机设置计算机。