PowerShell’s Security Guiding Principles

One of most common issues we face with PowerShell comes from users or ISVs misunderstanding PowerShell’s security guiding principles. At a high-level, it seems to all make sense – execution policies help ensure that you only run scripts that you trust on your system. These protections are driven by PowerShell’s three main security features. For people that will never use PowerShell, that trust decision is made for you by way of PowerShell’s default “Restricted” execution policy. Others may want to use a verifiable identity chain as their important guiding factor, in which case the Authenticode signing certificates (and associated identity) enforced by the “AllSigned” mode is ideal. Advanced scripters are typically very selective in the source of their scripts (and either deeply trust the source, or have reviewed the script itself) and select a RemoteSigned or Unrestricted execution policy.

These features are the core defenses in PowerShell’s security threat model.

The threat model of an application identifies:

  • What you are trying to protect. In PowerShell’s case, this is almost entirely “code execution.”
  • Sources of data, and how that data flows. In PowerShell’s case, these are scripts sent to you through email, scripts downloaded from the internet, your profile, user input, and other similar sources. From there, this data flows through many PowerShell features – the parser, cmdlet invocation, formatting and output, etc.
  • Boundaries between untrusted data and trusted data.
    • PowerShell doesn’t trust scripts that you download from the internet.
    • PowerShell doesn’t trust a random script or executable lying in the current location of your hard drive.
    • PowerShell does trust user input.
    • PowerShell does trust the administrator of the machine.
    • PowerShell does trust a running script.

Security features come into play any time information crosses a trust boundary. PowerShell doesn’t trust scripts that you’ve downloaded from the internet, so the Execution Policy gives you a way to usher the script across that trust boundary. Once you trust it, PowerShell trusts it, and runs it faithfully.

Example: A malicious script uses the .NET Reflection APIs to modify internal engine data structures. It creates the ability to send all of your output to a secret server somewhere in the bowels of the internet. Is this a security bug?

Answer: No. PowerShell didn’t trust the script when you got it, but your choice of execution policy declared it as safe. That means it is now trusted and should operate with full functionality. After all, what kind of programming language would PowerShell be without support for TCP scripting?

PowerShell trusts user input, and runs it as-is. PowerShell’s formatting and output system trusts the objects that arrive to it, and format them however is requested.

Now, this is where things tend to get confused. People easily understand the power of an execution policy to prevent scripts from running, but often forget to consider from whom. They might think of enforcing an “AllSigned” policy as a way to prevent the user from running non-approved applications, when it is designed as a way to prevent the attacker from running scripts that the user doesn’t approve. This misconception is often wrongly reinforced by the location of V1’s ExecutionPolicy configuration key – in a registry location that only machine administrators have access to.

System-wide PowerShell Execution Policies have never been a way to prevent the user from doing something they want to do. That job is left to the Windows Account Model, which is a security boundary. It controls what a user can do: what files they can access, what registry keys they can access, etc. PowerShell is a user-mode application, and is therefore (by the Windows security model) completely under the user’s control.

Execution Policies are user feature. Like seatbelts. It’s best to keep them on, but you always have the option to take them off. PowerShell’s installer sets the execution policy to “Restricted” as a safe default for the vast majority of users that will never run a PowerShell script in their life. A system administrator might set the execution policy to AllSigned because they want to define it as a best practice, or let non-technical users run a subset of safe scripts. At any time, the user can decide otherwise:

  • Type the commands by hand
  • Paste the script into their PowerShell prompt
  • Call Invoke-Expression (Get-Content <script>)
  • Call PowerShell –Command (Get-Content <script>)
  • Use our PowerShell hosting APIs to host PowerShell with a different Authorization Manager
  • Write their own minishell that has a “Say Yes To Everything” Authorization Manager
  • Launch PowerShell in a debugger, and skip the statements that verify the Execution Policy
  • Type PowerShell commands that use private reflection to switch the Authorization Manager
  • Decompile System.Management.Automation, hack out the Authorization Manager code, and recompile it
  • Use a resource editing tool to modify the LUA manifest, and let Vista do registry virtualization into HKCU
  • Launch PowerShell in a 3rd-party registry or application virtualization program such as SoftGrid or  ThInstall
  • Use the Application Compatibility toolkit to write your own shim
  • Inject API hooks into PowerShell.exe (Overview, Tool, SDK)
  • (etc)

These are all direct results of Windows’ core security tenet: you have complete control over any process you are running.

PowerShell V2 makes this reality much more transparent through a concept called “Execution Policy Scopes.” In V1, the scopes are as follows. Items on top, if defined, override items below them:

  1. Machine-Wide Group Policy
  2. Current-User Group Policy
  3. Machine-Wide ExecutionPolicy (stored in HKLM)

In V2, the scopes are as follows, with “Process“, ”CurrentUser”, and “LocalMachine” now surfaced as the –Scope parameter to Set-ExecutionPolicy

  1. Machine-Wide Group Policy
  2. Current-User Group Policy
  3. ExecutionPolicy parameter to PowerShell.exe
  4. PSExecutionContext environment variable
  5. Current-User ExecutionPolicy (stored in HKCU)
  6. Machine-Wide ExecutionPolicy (stored in HKLM)

At its core, this refinement lets administrators and users tailor their safety harness. Jane might be fluent and technical (and opt for a RemoteSigned execution policy,) while Bob (another user of the same machine with different security preferences) can still get the benefits of an AllSigned default execution policy. In addition, agents or automation tools can invoke PowerShell commands without having to modify the permanent state of the system.

A similar misunderstanding is that PowerShell’s authorization policies somehow protect you from malware already running on your machine. Another core Windows security tenet is that any program you run has the same capabilities as you do.  This includes Vista’s UAC. It is not a security boundary.

Now, why is

PowerShell.exe –ExecutionPolicy Bypass –File c:\temp\bad-script.ps1

not a security bug? Ultimately, if bad code has the ability to run this code, it already has control of the machine.

Lee Holmes [MSFT]
Windows PowerShell Development
Microsoft Corporation
This posting is provided “AS IS” with no warranties, and confers no rights.