Cracking Open PowerShell’s Constrained Runspace

Recently at the PowerShell Summit, Lee Holmes and I did a talk on PowerShell security. One of the demonstrations we did showed how to find and exploit a command injection bug in a constrained runspace. I figured I’d write a short blog post on how to use a command injection bug to turn a constrained runspace in to an unconstrained runspace.

For those unaware, constrained runspaces are a pretty neat feature of PowerShell. A constrained runspace is a PowerShell Remoting endpoint that only allows the user to execute whitelisted PowerShell commands. The endpoint can optionally be configured to run these commands under a delegated account (such as an administrator account). This allows non-administrator users to connect to the endpoint and run specific, whitelisted commands, under a more privileged account. This makes constrained runspaces an excellent way to manage systems without requiring unfettered administrator access on every system.

For this blog, I’ve created an easy to install constrained runspace to practice attacking that can be found here: https://github.com/clymb3r/Demos/tree/master/VulnerableConstrainedRunspace. When you connect to this constrained runspace, you will have access to a limit set of commands (some of which are custom functions that I’ve written). Here is the output of “Get-Command” when running in my constrained runspace:

Running Get-Command in a constrained runspace.

Running Get-Command in a constrained runspace.

As you can see, very few commands are available. In a typical enterprise setup, I’d expect to see more commands then this available (and many of them might be custom functions written by the administrators). One thing to check is if the commands exposed allow for command injection. If you’ve read my previous blog post on safe PowerShell coding (https://clymb3r.wordpress.com/2013/09/07/avoiding-powershell-command-injection-unicode-issues/), you’ll know that sometimes programmers concatenate user input (taken from the function parameters) with other data and use the resulting string to call Invoke-Expression (or similar functions).

Below is an example of command injection in the runspace I have provided. The function Get-ChildItemProxy1 is only supposed to execute “Get-ChildItem” with the user supplied path.

The Get-ChildItemProxy1 function. The script is only intended to execute Get-ChildItem but can be made to execute arbitrary code due to a command injection bug.

The Get-ChildItemProxy1 function. The script is only intended to execute Get-ChildItem but can be made to execute arbitrary code due to a command injection bug.

Due to a programming error, an attacker is able to inject arbitrary commands to execute. In the example below, the attacker forces the function to also execute “Get-Process”:

Basic PowerShell command injection in a constrained runspace.

Basic PowerShell command injection in a constrained runspace.

This is obviously extremely powerful, but it’s also annoying to use. For every command you wish to execute you must properly escape your input as to properly exploit the command injection vulnerability. Can we do better? Of course!

If you run Get-Command, you’ll notice one of the properties that functions/cmdlets have is a “Visibility” property. In a normal PowerShell session, all functions are marked “Public”. When you setup a constrained runspace PowerShell sets the visibility of all non-whitelisted functions to “Private”. This prevents you from using the function/cmdlet from the constrained runspace, but commands that are whitelisted (such as Get-ChildItemProxy1) are allowed to call “Private” functions.

All you need to do to turn a constrained runspace in to an unconstrained runspace is mark all functions as “Public”. How do you do that? Check this out:

Get-ChildItemProxy1 "c:\'; Get-Command | ForEach-Object { `$_.Visibility='Public' };'"

This command once again exploits the command injection. It uses command injection to call “Get-Command”. Remember that because “Get-Command” is being called from inside a whitelisted function, it is able to see all functions/cmdlets (including those that have been marked “Private”). For each command, it sets the “Visibility” to “Public”. Now if you run “Get-Command”, you’ll see you have significantly more commands available to use.

How to turn a constrained runspace in to an unconstrained runspace with PowerShell command injection.

How to turn a constrained runspace in to an unconstrained runspace with PowerShell command injection.

Thanks to Lee Holmes (@Lee_Holmes) for showing me some of these tricks!

Advertisements
Tagged with: , , ,
Posted in Hacking, PowerShell
2 comments on “Cracking Open PowerShell’s Constrained Runspace
  1. gbrayut says:

    Very interesting. I am curious, does changing the visibility of commands produce an audit-able event? Creating constrained runspaces is a great way to protect systems while still allowing Powershell for management and remote access. If you only expose a limited number of commands you could be reasonably confident about prevent this kind of attack, but as the number of command increases it may be harder to prevent this from occurring.

    Is something like Powershell DSC ably to detect these changes and alert to the problem or automatically change the commands back to being private. Or maybe using pipeline logging to detect changes in command visibility?

    • clymb3r says:

      DSC won’t be able to detect this since it happens in your PowerShell session (the changes are never written to disk or anything like that). Pipeline logging can certainly be used to detect this. You can also create a transcript of every PowerShell remoting session and offload the transcripts to another server so they hopefully cannot be tampered with. Both of these would allow you to detect if someone is trying to break your constrained runspace.

Leave a Reply to gbrayut Cancel reply

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: