如何防止 PowerShell 中的变量注入?
How can I prevent variable injection in PowerShell?
@Ansgar Wiechers 对最近的 PowerShell 问题的评论再次触发了我:DO NOT use Invoke-Expression
关于一个安全问题,我已经在脑海深处想了很久,需要问一下.
强声明(参考 Invoke-Expression considered harmful 文章)表明调用可以覆盖变量的脚本被认为是有害的。
还有PSScriptAnalyzer advises against using Invoke-Expression
, see the AvoidUsingInvokeExpression rule.
但我自己曾经使用一种技术来更新递归脚本中的一个公共变量,它实际上可以覆盖其任何父作用域中的值,这很简单:
([Ref]$ParentVariable).Value = $NewValue
据我所知,潜在的恶意脚本也可以使用此技术在任何情况下注入变量,无论它是如何调用的...
考虑以下 "malicious" 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
有没有办法将调用的 script/command 与覆盖当前作用域中的变量完全隔离?
如果不是,这是否可以被视为以任何方式调用脚本的安全风险?
反对使用 Invoke-Expression
的建议主要是为了防止意外 执行代码(代码注入)。
如果您调用一段 PowerShell 代码 - 无论是直接调用还是通过 Invoke-Expression
- 它确实可以(可能是恶意地)操纵父作用域,包括全局作用域。
请注意,这种潜在的操作不限于 variables:例如,functions 和 aliases 可以是也修改了。
警告:运行未知代码在两个方面有问题:
- 主要是为了有可能 直接 执行不需要的/破坏性的操作。[1]
- 其次,因为有可能恶意修改调用者的状态(变量,...),即只有方面下面的解决方案防范.
要提供所需的隔离,您有两个基本选择:
运行 child 进程中的代码:
通过启动另一个 PowerShell 实例;例如(在 Windows PowerShell 中使用 powershell
而不是 pwsh
):
pwsh -c { ./someUntrustedScript.ps1 }
通过启动后台作业;例如:
Start-Job { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
运行代码在一个单独的线程在同一个进程中:
作为线程作业,通过Start-ThreadJob
cmdlet(PowerShell [Core] 6+ 附带;在Windows PowerShell 中, 它可以安装 from the PowerShell Gallery 类似
Install-Module -Scope CurrentUser ThreadJob
);例如:
Start-ThreadJob { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
通过 PowerShell SDK 创建一个 new 运行space;例如:
[powershell]::Create().AddScript('./someUntrustedScript.ps1').Invoke()
- 请注意,您必须做额外的工作才能获得输出流 other 而不是成功的输出流,特别是错误流的输出;此外,
.Dispose()
应在命令完成后在 PowerShell 实例上调用。
基于子进程的解决方案会很慢,并且在您可以使用的数据类型方面受到限制return(由于涉及序列化/反序列化),但它提供对崩溃进程的调用代码的隔离。
thread-based 作业要快得多,可以 return 任何数据类型,但可能会导致整个过程崩溃。
在所有情况下,您都必须将调用代码需要访问的调用方的任何值作为 参数 传递,或者对于后台作业和线程作业,也可以通过 $using:
范围说明符。
js2010 提到了其他不太理想的替代方案:
Start-Process
(子 process-based,带有 text-only 个参数和输出)
PowerShell Workflows,已过时(它们未移植到 PowerShell Core,以后也不会)。
将 Invoke-Command
与“环回远程处理”(-ComputerName localhost
) 一起使用假设也是一种选择,但是您会招致子进程的双重开销和 HTTP-based 沟通;此外,您的计算机必须设置为远程处理,并且您必须 运行 提升权限(作为管理员)。
[1] 缓解问题的一种方法是限制允许哪些命令、语句、类型...在评估字符串时调用,这可以通过PowerShell SDK in combination with language modesand/or by explicitly constructing an initial session state. See 实现,以获取SDK使用语言模式的示例。
@Ansgar Wiechers 对最近的 PowerShell 问题的评论再次触发了我:DO NOT use Invoke-Expression
关于一个安全问题,我已经在脑海深处想了很久,需要问一下.
强声明(参考 Invoke-Expression considered harmful 文章)表明调用可以覆盖变量的脚本被认为是有害的。
还有PSScriptAnalyzer advises against using Invoke-Expression
, see the AvoidUsingInvokeExpression rule.
但我自己曾经使用一种技术来更新递归脚本中的一个公共变量,它实际上可以覆盖其任何父作用域中的值,这很简单:
([Ref]$ParentVariable).Value = $NewValue
据我所知,潜在的恶意脚本也可以使用此技术在任何情况下注入变量,无论它是如何调用的...
考虑以下 "malicious" 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
有没有办法将调用的 script/command 与覆盖当前作用域中的变量完全隔离?
如果不是,这是否可以被视为以任何方式调用脚本的安全风险?
反对使用 Invoke-Expression
的建议主要是为了防止意外 执行代码(代码注入)。
如果您调用一段 PowerShell 代码 - 无论是直接调用还是通过 Invoke-Expression
- 它确实可以(可能是恶意地)操纵父作用域,包括全局作用域。
请注意,这种潜在的操作不限于 variables:例如,functions 和 aliases 可以是也修改了。
警告:运行未知代码在两个方面有问题:
- 主要是为了有可能 直接 执行不需要的/破坏性的操作。[1]
- 其次,因为有可能恶意修改调用者的状态(变量,...),即只有方面下面的解决方案防范.
要提供所需的隔离,您有两个基本选择:
运行 child 进程中的代码:
通过启动另一个 PowerShell 实例;例如(在 Windows PowerShell 中使用
powershell
而不是pwsh
):pwsh -c { ./someUntrustedScript.ps1 }
通过启动后台作业;例如:
Start-Job { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
运行代码在一个单独的线程在同一个进程中:
作为线程作业,通过
Start-ThreadJob
cmdlet(PowerShell [Core] 6+ 附带;在Windows PowerShell 中, 它可以安装 from the PowerShell Gallery 类似Install-Module -Scope CurrentUser ThreadJob
);例如:Start-ThreadJob { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
通过 PowerShell SDK 创建一个 new 运行space;例如:
[powershell]::Create().AddScript('./someUntrustedScript.ps1').Invoke()
- 请注意,您必须做额外的工作才能获得输出流 other 而不是成功的输出流,特别是错误流的输出;此外,
.Dispose()
应在命令完成后在 PowerShell 实例上调用。
基于子进程的解决方案会很慢,并且在您可以使用的数据类型方面受到限制return(由于涉及序列化/反序列化),但它提供对崩溃进程的调用代码的隔离。
thread-based 作业要快得多,可以 return 任何数据类型,但可能会导致整个过程崩溃。
在所有情况下,您都必须将调用代码需要访问的调用方的任何值作为 参数 传递,或者对于后台作业和线程作业,也可以通过 $using:
范围说明符。
js2010 提到了其他不太理想的替代方案:
Start-Process
(子 process-based,带有 text-only 个参数和输出)PowerShell Workflows,已过时(它们未移植到 PowerShell Core,以后也不会)。
将
Invoke-Command
与“环回远程处理”(-ComputerName localhost
) 一起使用假设也是一种选择,但是您会招致子进程的双重开销和 HTTP-based 沟通;此外,您的计算机必须设置为远程处理,并且您必须 运行 提升权限(作为管理员)。
[1] 缓解问题的一种方法是限制允许哪些命令、语句、类型...在评估字符串时调用,这可以通过PowerShell SDK in combination with language modesand/or by explicitly constructing an initial session state. See