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

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:

Machine-Wide Group Policy

Current-User Group Policy

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

Machine-Wide Group Policy

Current-User Group Policy

ExecutionPolicy parameter to PowerShell.exe

PSExecutionContext environment variable

Current-User ExecutionPolicy (stored in HKCU)

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.

Some machines are configured to treat UNC paths as the same security zone as the internet (as opposed to the intranet.) This is Internet Explorer’s "Enhanced Security Configuration." In this case, PowerShell responds the same as the Explorer Shell when it runs scripts from a UNC path: “While scripts from the internet can be useful, this script can potentially harm your computer. Do you want to run <script>?”

This could solve a huge problem we have. Running PS scripts in w2k8 environment is not very easy and there are already people asking if we could just use cmd batches instead (PS from SCCM). I understand that Unrestricted will ask you if you wanted to run downloaded script – however you got the same if you run script from network share in secured environment (UNC paths are automatically considered unsecured) 🙁