How can I prevent variable injection in PowerShell?

二次信任 提交于 2019-12-19 04:01:12

问题


I was triggered again on a comment on a recent PowerShell question from @Ansgar Wiechers: DO NOT use Invoke-Expression with regards to a security question I have for a long time somewhere in the back of my mind and need to ask.

The strong statement (with a reference to the Invoke-Expression considered harmful article) suggests that an invocation of a script that can overwrite variables is considered harmful.

Also the PSScriptAnalyzer advises against using Invoke-Expression, see the AvoidUsingInvokeExpression rule.

But I once used a technic myself to update a common variable in a recursive script which can actually overwrite a value in any of its parents scopes which is as simple as:

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

As far as I can determine a potential malicious script could use this technic too to inject variables in any case no matter how it is invoked...

Consider the following "malicious" Inject.ps1 script:

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

My Test.ps1 script:

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

Result:

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

As you see all three variables in the Test.ps1 scope are overwritten by the Inject.ps1 script. This can also be done using the Invoke-Command cmdlet and it doesn't even matter whether I set the scope of a variable to Private either:

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

Is there a way to completely isolate an invoked script/command from overwriting variables in the current scope?
If not, can this be considered as a security risk for invoking scripts in any way?


回答1:


The advice against use of Invoke-Expression use is primarily about preventing unintended execution of code (code injection).

If you do consciously invoke a piece of PowerShell code, it can indeed (possibly maliciously) manipulate parent scopes, including the global scope.
Note that this potential manipulation isn't limited to variables: for instance, functions and aliases can be modified as well.

To provide the desired isolation, you have two basic choices:

  • Run the code in a child process:

    • By starting another PowerShell instance; e.g. (use powershell instead of pwsh in Windows PowerShell):

      • pwsh { ./someUntrustedScript.ps1 }
    • By starting a background job; e.g.:

      • Start-Job { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
  • Run the code in a separate thread:

    • As a thread job, via the ThreadJob module (comes with PowerShell Core and in Windows PowerShell can be installed from the PowerShell Gallery with something like
      Install-Module -Scope CurrentUser ThreadJob); e.g.:

      • Start-ThreadJob { ./someUntrustedScript.ps1 } | Receive-Job -Wait -AutoRemove
    • By creating a new runspace via the PowerShell SDK; e.g.:

      • [powershell]::Create().AddScript({ ./someUntrustedScript.ps1 }).Invoke()
      • Note that you'll have to do extra work to get the output streams other than the success one, notably the error stream's output; also, .Dispose() should be called on the PowerShell instance on completion of the command.

A child process-based solution will be slow and limited in terms of data types you can return (due to serialization / deserialization being involved), but it provides isolation against the invoked code crashing the process.

A thread-based job is much faster, can return any data type, but can crash the entire process.

In all cases you will have to pass any values from the caller that the invoked code needs access to as arguments.


js2010 mentions other, less desirable alternatives:

  • Start-Process (child process-based, with text-only arguments and output)

  • PowerShell Workflows, which are obsolescent (they weren't ported to PowerShell Core and won't be).

  • Using Invoke-Command with "loopback remoting" (-ComputerName localhost) is hypothetically also an option, but then you incur the double overhead of a child process and HTTP-based communication; also, your computer must be set up for remoting.



来源:https://stackoverflow.com/questions/56985269/how-can-i-prevent-variable-injection-in-powershell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!