How to Secure Your PowerShell
PowerShell is a very powerful tool that can be used to do amazing things in IT Infrastructure automation. However, it also can be used for more malicious reasons — with the possibility to create real damage to our environments. PowerShell security helps mitigate such risks as much as possible — mainly by using code signing and execution policy
The default configuration of PowerShell, with security in mind, is set to not allow PowerShell scripts to be run by double-clicking them. Scripts need to be digitally signed by a certificate, which is trusted on the client computer. Web browser-downloaded scripts have a mark in their meta-data blocking them by default — and the script will always run in the context of the user.
Here's a look at ways to secure your PowerShell environment and rest a little easier.
Why Implement PowerShell Secured Scripts
PowerShell Secured Scripts is a feature that you don't need it until you need it. In small organizations where the management of IT infrastructure is delegated to a handful of employees, running unsigned scripts is a normal scenario — because the infrastructure landscape is small and there is much more transparent communication inside the team. Especially knowing who's doing what.
In an enterprise environment where management of such an infrastructure will be delegated across teams, sometimes in different geographical regions, it is complicated to control who's running what and on which systems. Even more challenging can be ensuring the proper people run the correct scripts.
All of this opens the door to potential issues such as control of execution, command hijack, and identity and integrity.
In programming, execution control refers to the order a program runs statements. In PowerShell, it refers to which scripts the system runs and Windows has four types of execution control policies: Restricted, AllSigned, RemoteSigned and Unrestricted.
Restricted is the most, restrictive policy, where the system does not allow running any type of script — local, remote or downloaded.
AllSigned forces all scripts to be digitally signed using a certificate, otherwise the system will not run them.
RemoteSigned allows remote UNC and downloaded scripts to run only if they are digitally signed. Local scripts can be run regardless.
Unrestricted will be found in most companies, especially small ones where you have only one systems administrator who does not need to implement a script execution policy. In this case, all scripts run, regardless if they are digitally signed or not.
Code Signing, identity, and integrity are all a subset of certificate usage in running PowerShell scripts. By using certificates to authenticate scripts, we start to separate which scripts have been developed, tested and ran by specific individuals, and which have been downloaded from the internet — and have at least tried to be run by less privileged users or end users.
Just to give you an example, users could look for all types of PowerShell scripts to automate a Microsoft Office task or other Windows Client OS component. What they don't know or can't do is make sure the script is not malicious, does not include bad code, and that it actually does what it's intended to do.
By requiring all systems in the enterprise to only run signed scripts, we can ensure that 99% of the scripts are not malicious and are actually doing what they are intended to do.
How to Implement Security
To implement code signing in PowerShell, we can either use ADCS or buy an actual code signing certificate from a certification authority, like GoDaddy or VeriSign.
If we are using ADCS (Active Directory Certificate Services), we will need to add a code-signing template and allow only specific users to request that template. Once an authorized user receives a code signing certificate, they can take that certificate and sign the PowerShell Script by using a PowerShell cmdlet called Set-AuthenticodeSignature.
First we need to assign the code signing certificate to a variable by using the following syntax:
$cscert = (dir Cert:\CurrentUser\My -CodeSigningCert)
The above command enumerates the certificates in the Current User certificate store of the type Code Signing Certificate. Because the output will be an array, by using  we will select the first certificate in the array, wherein many cases will be the only certificate in the array.
Next, we sign our PowerShell script with the specific certificate:
Set-AuthenticodeSignature .\myscript.ps1 -Certificate $cscert
To make sure that the PowerShell script has been signed correctly, we can use the following cmdlet:
Get-AuthenticodeSignature .\myscript.ps1 | ft -AutoSize
The output we should receive looks like the following:
SignerCertificate Status Path
—————– —— —-
9854ABA48101875C8D9A7F79F3CC9AS1C911F73C Valid myscript.ps1
Now that we understand how to implement code signing in PowerShell, we can look at and research how an attacker can bypass this execution policy and actually run a PowerShell script that is not signed.
To further mitigate the running of scripts, we can use GPO (Group Policy Objects) to configure which users can run scripts at all in the system by configuring a Restricted Policy for users that are part of a specific AD Group — or are accessing specific machines.
By taking some of the steps we outlined in this post, you can more effectively secure PowerShell. Good luck with your future scripting projects.