If, like me, you use PowerShell on a daily basis then I imagine it’s likely you have come across Execution Policies before. An Execution Policy is a concept within PowerShell that helps deliver a more secure command line administration experience. These policies define the restrictions under which PowerShell loads files for execution and configuration. There are four execution policies to choose from; Restricted, AllSigned, RemoteSigned and Unrestricted. I’ve come across many environments where the Execution Policy has been relaxed too far to simply execute a PowerShell script (.ps1) for various reasons; scheduled tasks, installation scripts, etc. The problem is, how do we ensure an environment’s security isn’t compromised in this manner and the overall integrity is maintained, while still being able to run these scripts? Simple – you need to get the script signed and relax the Execution Policy only to the required level.
The most secure (and also the default) policy in which PowerShell will operate as an interactive shell only, it will not allow the execution of PowerShell scripts.
The AllSigned policy allows scripts to be run, but all scripts and configuration files must be signed by a publisher that is trusted by the system. In turn, this execution policy presents the risk of running signed, but malicious, scripts after confirming that you trust the publisher. In my opinion, this is the lowest policy configuration for a Production environment, and only on appropriate servers, as it enforces a requirement for digital signatures on all PowerShell scripts and configuration files.
Allows for scripts to be run, but all scripts and configuration files downloaded from communication applications (such as Microsoft Outlook, Internet Explorer, Windows Messenger, etc) must be signed by a published that is trusted by the system. In turn, this presents the risk of running malicious scripts downloaded from these applications without first being prompting.
The unrestricted Execution Policy should be very rarely used as it allows execution of any script and configuration file downloaded from communication applications, after confirming “you understand the file originated from the Internet”. No digital signature is required and as a result, is the most unsecure policy as it presents the risk of running unsigned, malicious scripts.
If you attempt to run a PowerShell script that has been blocked by an Execution Policy, then the following message is returned:
The file [ScriptName.ps1] cannot be loaded. The execution of scripts is disabled on this system. Please see "Get-Help about_signing" for more details.
Execution Policy Configuration
The Execution Policy for PowerShell can be changed as per requirement and are recognised immediately. For example, to enable the AllSigned policy, run the following from an elevated PowerShell session:
Adding a digital signature to a script requires that it be signed with a code signing certificate. Two types are suitable; those created by a Certificate Authority, and those created by a user (or a self-signed certificate). For a self-signed certificate, the designated computer becomes the authority that creates the certificate. Code signing certificates may be purchased from another Certificate Authority (such as VeriSign) or, if your environment permits, created on your Enterprise Root Certificate Authority.
The advantages of using self-signed certificates are; zero cost, creation speed and convenience. However, that certificate needs to be installed on any computer that intends to run the script.
To create a self-signed certificate, the makecert.exe program is required. This is freely available in the Microsoft .NET Framework SDK or the Microsoft Windows Platform SDK. After installation, the default installation directory is C:Program FilesMicrosoft.NETSDKv2.0 and the makecert,exe application is located in the .Bin directory. For further information regarding makecert.exe and it’s usage, please refer to this great MSDN article.
- Click Start > Run
- Type mmc into the Run dialog then click Ok
- Click File and select Add/Remove Snap-in
- From the Add/Remove Snap-in dialog, select Certificates and click Add
- When prompted, select My User Account and click Finish
- Now click Ok on the Add/Remove Snap-in dialog
Self-Signed Certificate Creation
- First, you need to creat a local certificate authority. Open an elevated Command PRompt and type the following:
makecert -n "CN=PowerShell Local Certificate Root" -a sha1 -eku 188.8.131.52.184.108.40.206.3 -r -sv root.pvk root.cer -ss Root -sr localMachine
- When prompted, enter a private key password and confirm, then press Ok.
- You will be prompted for the private key password again, once entered press Ok
- The trusted root certificate authority will be created on the computer account
- Now run the following from Command Prompt to generate a personal certificate from the certificate authority created in the previous steps
makecert -pe -n "CN=PowerShell User" -ss MY -a sha1 -eku 220.127.116.11.18.104.22.168.3 -iv root.pvk -ic root.cer
- You will be prompted for the private key entered before
- The certificate will now be generated in the Personal store for the current user
- Open PowerShell and run the following to verify that the certificate was correctly generated
Get-ChildItem cert:CurrentUserMy –codesign
- The root.pvk and root.cer temporary files can now be removed (the default working directory is C:Program FilesMicrosoft.NETSDKv2.0Bin)
- Note: Do not remove the any contents of C:Users[UserName]Application DataMicrosoftSystemCertificatesMy
Signing a Script
- Firstly, to test the effectiveness of digitally signing a PowerShell script, ensure the execution policy is set to AllSigned. Create a PowerShell script called PS_SignTest.ps1 with the following contents
Get-Service | Format-Wide -Column 3
- Attempt to execute the script from PowerShell and observe the error message
- Sign the script by running the following from the PowerShell console
Set-AuthenticodeSignature .PS_SignTest.ps1 @(Get-ChildItem cert:CurrentUserMy -codesigning)
- Edit the script and observe that a signature block has been added. Note that the signature content block will appear differently for each signed script
Get-Service | Format-Wide -Column 3 # SIG # Begin signature block # MIIEMwYJKoZIhvcNAQcCoIIEJDCCBCACAQExCzAJBgUrDgMCGgUAMGkGCisGAQQB … snip # RL5ufUjpD27yHnsAyvETKdQE4L4taoQ= # SIG # End signature block
- Now the script has been signed, attempt to execute the script again. You will receive a notification that the publisher is untrusted. Select option A to Always Run and a new certificate will be created in the current users’ trusted publisher certificate store
This post is intended for reference and educational purposes only. Any change to the execution policies are taken at your own risk and no liability can be taken from this post. If you do not understand the risks, do not make changes to your system.