Quantcast
Channel: clymb3r » clymb3r
Viewing all articles
Browse latest Browse all 9

Avoiding PowerShell Command Injection & Unicode Issues

$
0
0

PowerShell exposes a powerful set of functionality and is increasing in popularity for server management tasks. This post aims to help penetration testers identify issues that may be found when PowerShell scripts handle user input. There are multiple scenarios where this might occur, but two that stand out to me are:

1. PowerShell scripts which are whitelisted in a constrained run space

Constrained run spaces can be used to allow a non-administrative user to remote PowerShell in to a server and execute a set of whitelisted scripts/commands. Delegation can be setup, allowing these scripts to be executed as an administrator even though the user is a non-administrator. If these scripts contain injection bugs, users can elevate themselves to administrators.

2. PowerShell scripts which are executed with user input taken from a service of some sort (such as a web site)

In this scenario a web site might take user input and pass it to a PowerShell script to perform some sort of action. Injection bugs in the PowerShell script will lead to remote code execution against the website.

In both of these scenarios, the PowerShell script being executed might contain flaws that allow command injection. Lets look at a few examples:

Param(
 [String]$UserInput
)

#####################
#Exploit Invoke-Command and Invoke-Expression
#To exploit: .\cmdinjection -UserInput "c:\'; calc.exe;'"
$NewCmd = "Get-ChildItem '$UserInput'"

#Unsafe Invoke-Expression
Write-Output "Invoke-Expression unsafe demo"
Invoke-Expression $NewCmd

#Unsafe Invoke-Command
Write-Output "Invoke-Command unsafe demo"
[ScriptBlock]$sb = [ScriptBlock]::Create($NewCmd) #Cast the command string to be a scriptblock
Invoke-Command -ScriptBlock $sb
#####################

None of this is mind blowing. Dynamic script creation leads to bad things in the same way that dynamic SQL creation leads to bad things.

Here is one cool issue you might not expect though. Lets say you decide to use dynamic script creation anyways, but you are clever and you ensure user input doesn’t contain a tick mark to prevent command injection.

Param(
 [String]$UserInput
)

#####################
#Exploit Invoke-Command with regex filter
#To exploit: $escapeChar = [char]8216
# .\cmdinjection2.ps1 "c:\$escapeChar; calc.exe $escapeChar"
if ($UserInput -imatch "'")
{
 Write-Error "Malicious user input found!" -ErrorAction Stop
}

$NewCmd = "Get-ChildItem '$UserInput'"

#Unsafe Invoke-Command
Write-Output "Invoke-Command unsafe demo"
[ScriptBlock]$sb = [ScriptBlock]::Create($NewCmd) #Cast the command string to be a scriptblock
Invoke-Command -ScriptBlock $sb
#####################

While at first glance this appears to work, PowerShell has a feature which interprets smart quotes as normal quotes. When you type quotes or ticks in to Outlook or Word, they are automatically converted to Unicode characters (opening and closing quotes/ticks). When people copy and pasted script sent to them in Outlook or in Word documents, the scripts wouldn’t work because these Unicode quotes didn’t tokenize in to normal quotes that PowerShell expects. To make life easier, PowerShell now tokenizes Unicode quotes and ticks the same as ASCII quotes and ticks.

Here’s a look at the relevant part of the PowerShell Tokenizer:

Code from the PowerShell tokenizer which treats unicode ticks/quotes the same as ASCII ticks/quotes.

Code from the PowerShell tokenizer which treats unicode ticks/quotes the same as ASCII ticks/quotes.

This means the above regex can be bypassed by using a Unicode tick, which bypasses the regex filter, but is tokenized as a tick when PowerShell interprets it. Try running the script like this:

PowerShell regex bypass using unicode tick marks which can allow command injection.

PowerShell regex bypass using unicode tick marks which can allow command injection.

So what is the solution? Don’t use string concatenation to create script.

If you are taking user input and using it as a parameter to a PowerShell command, do it like this (rather than building a string to execute):

Param(
 [String]$UserInput
)

#####################
#Safe example

Get-ChildItem $UserInput

#####################

If you are taking user input and building a script to run on a remote computer using PowerShell remoting, do it like this:

Param(
 [String]$UserInput
)

#####################
#Safe example

[ScriptBlock]$SafeWay = {
 Param(
 [String]$Path
 )

Get-ChildItem $Path
}

Invoke-Command -ScriptBlock $SafeWay -ArgumentList $UserInput -ComputerName "AnotherServer"
#####################

Last but not least, I’d like to thank Chris Weber and John Hernandez from Casaba for helping me quickly identify the root cause of the regex bypass I discovered.



Viewing all articles
Browse latest Browse all 9

Trending Articles