<

Powershell Script Signing

Published on
40,664 Points
33,764 Views
9 Endorsements
Last Modified:
Approved
How to sign a powershell script so you can prevent tampering, and only allow users to run authorised Powershell scripts

Introduction


In today's security conscious world, we - as Administrators - need to be security conscious too.  This means, taking all the necessary steps to safeguard our users and systems.  Powershell Scripts - while not new - is still something that a lot of people use in a way that's not entirely safe.  Powershell's default execution policy - "Restricted" pretty much means "You can't run any scripts at all".  Naturally, we want to run scripts, so we change this - but what do we change it to?  "Unrestricted" of course - and that there is the problem.  We've just disabled a good security feature.
 

Execution Policies


The possible execution policies in Powershell are as follows:
 
  • Restricted: Does not load configuration files or run scripts. "Restricted" is the default execution policy.
  • AllSigned: Requires that all scripts and configuration files be signed by a trusted publisher, including scripts that you write on the local computer.
  • RemoteSigned: Requires that all scripts and configuration files downloaded from the Internet be signed by a trusted publisher.
  • Unrestricted: Loads all configuration files and runs all scripts. If you run an unsigned script that was downloaded from the Internet, you are prompted for permission before it runs.
  • Bypass: Nothing is blocked and there are no warnings or prompts.
  • Undefined: Removes the currently assigned execution policy from the current scope. This parameter will not remove an execution policy that is set in a Group Policy scope.

Seeing what the current execution policy is can be done in Powershell by issuing the command Get-ExecutionPolicy, and setting it is done via Set-ExecutionPolicy.  See the following screenshots for examples:

01---GetExecutionPolicy-Sample.pngThe two safest policies are: "RemoteSigned" and "AllSigned".  This guide will help you sign your scripts so that your scripts will be allowed to run under these two policies.
 

Proper PKI


In order to complete this guide, you do need to have a Certificate Authority.  Certificate Authority is pretty much a required component of any reasonable size corporation.  It's free with Windows Server, and can be deployed on a domain controller, or on a member server.  Prior to continuing this guide, please find your Certificate Authority, or create one for your organization.  This article will not cover using self-signed certificates, for the mere reason that self-signed certificates should not be used in a corporate environment.  We're trying to make things secure - let's do it properly :)  This guide will not cover creation of a Certificate Authority, but assuming one does not exist at EE, I'll create one.

Tip: Running the following command will bring up a box showing you which server is your Certificate Authority.
 
certutil -config - -ping

Open in new window

 

Enable Code Signing Template


The first piece of the puzzle is to enable the Code Signing template, which is not enabled by default.  Log on to your Certificate Authority as your Enterprise Administrator (or equivalent), open up the Certificate Authority administration snap-in, and then locate and select the "Certificate Templates" node.  Right click the node and then select "New" and then "Certificate Template to Issue"

02---New-Certificate-Tempalte-to-Iss.pngSelect "Code Signing" from the drop down list, and then click OK.  If "Code Signing" appears in the Certificate Templates window, then you're finished here - and you can log off the server.
 

Request Code Signing Certificate


On your computer, open up the Certificate Manager.  TIP: You can open this up easily by using Windows+R to open up the Run prompt and typing "certmgr.msc"  Once here, navigate to your Personal Certificates folder, and right click it, and select "All Tasks" and then "Request New Certificate..."

03---Request-New-Certificate.pngClick "Next" at the Before you Begin page, and then ensure that "Active Directory Enrollment Policy" is selected on the Select Certificate Enrollment Policy page, before clicking Next.

04---Active-Directory-Enrollment-Pol.pngCheck the "Code Signing" box, and then click "Enroll"

05---Enroll-for-Code-Signing.pngYou should see a Success.  You can use the drop down arrow next to "Details" to see more details about the certificate.

06---Enrollment-Success.pngWhen you are finished with that, you should now see your certificate as part of your personal certificate store.

07---My-Certificate.pngWhile you're at it, let's have a look at your certificate store using Powershell.  You will need this command for later anyway!  Use the following command:
 
Get-ChildItem cert:\CurrentUser\My -codesign

Open in new window


And it will output information similar to this:

08---Viewing-My-Code-Signing-Certifi.png

Signing your Powershell Script


If you view your powershell script right now, you'll see that it's fairly plain - and exactly what you typed originally.

09---Content-of-my-Script.pngI can run this script right now, because my script is local to my computer.  "RemoteSigned" means that it only has to be signed if I'm running it from a remote location, such as a network share.  The thing is, I want to share this script with all my corporate users, so it's better that it's in a central place.

We are now going to sign the script, using this command line:
 
Set-AuthenticodeSignature <filename> <SignerCertificate>

Open in new window


For me, it's this:
 
Set-AuthenticodeSignature .\myscript.ps1 @(Get-ChildItem cert:\CurrentUser\My -codesign)[0]

Open in new window


You will notice for SignerCertificate portion, I've re-used an earlier command I did.  One thing that's different though is that I've specified an index at the back ([0] refers to the first item in the array).  If you have multiple code signing signatures, you may need to change the index accordingly.

If all goes well, this is the output you should see:

10---Set-AuthenticodeSignature-outpu.pngAnd now, if we have a look at our script, it will look a bit more interesting:

11---Content-of-my-Script-Now.png


Running the Script


I'm now going to set my Execution Policy to "AllSigned", so that I'm going to force my script execution to require signing, even when running locally.

When Powershell has been told to require signatures, the first thing it's going to do is see if we've previously trusted the code signer. Despite the fact that we have a proper PKI in place, with a Certificate Authority that is trusted, it still wants the user to approve the code signer.  When you run the script, you will receive a prompt similar to the following:

Do you want to run software from this untrusted publisher?
File C:\Users\lclayton\myscript.ps1 is published by CN=Lester Clayton, OU=Users, OU=Support Accounts, DC=mgmt, DC=local and is not trusted on your system. Only run scripts from trusted publishers.
[V] Never run  [D] Do not run  [R] Run once  [A] Always run  [?] Help (default is "D"):
"R" will run the script once, but not add the code signer to trusted publishers, meaning that if I run the script again, I'll get the same prompt.
"A" will add the code signer to the users trusted publishers store, and then they'll never get prompted again for any scripts that are signed by that Code Publisher.

Had you selected "A", then if you view your Trusted Publishers Store now, you'll see that the certificate has appeared.  I am now a trusted publisher.

12---Trusted-Publishers.png


Automating Trusting Code Publishers


Of course, we don't want our users to be nagged by these prompts.  Using a Group Policy Object, it is possible to pre-install certificates for users inside an Active Directory Domain.  The GPO would look similar to this:

13---GPO-for-Authorizing-Trusted-Pub.png


Tips & Tricks


Just remember that by doing the right thing and enforcing code signing, you are going to have to commit to a bit more maintenance.  Certificates expire over time, and with each modification you make to your script, you are going to have to re-sign the certificate, otherwise the certificate validation will fail.

The default validity period for the Code Signing template is 1 year.  If you duplicate that template, you can set the validity period to be even longer.  Your Certificate Authority has most likely got a long validity period (5 years or more), so you can set the validity period to be the same.  NOTE: You cannot have an issued certificate validity longer than your CA's current validity period - so you can't issue a certificate for 99 years when your CA expires in 3!

Using Powershell, you could sign all the scripts in a folder with the same signature by using something similar to the following:
 
$MySignature = @(Get-ChildItem cert:\CurrentUser\My -codesign)[0]
Get-ChildItem "\\mgmt.local\SYSVOL\mgmt.local\scripts" -Filter *.ps1 | 
Foreach-Object { Set-AuthenticodeSignature $_.FullName $MySignature }

Open in new window


Don't forget that you can also force an execution policy through Group Policies, so that users (or even other Administrators) can't change their Execution Policy back to "Bypass"

This extra maintenance is worth the effort, to bring you peace of mind:
  • Users will not be able to modify your scripts that you've signed and run them
  • Users will not be able to run scripts which they've downloaded from the Internet (unless they've been signed by a trusted Certificate Authority - which is unlikely!)
  • Your Administrator "friends" can't tamper with your code making Troll changes for a laugh.
I hope this guide has been of use to you - protect your code today!
9
Comment
2 Comments
LVL 66

Expert Comment

by:Jim Horn
Great article, and probably the best use of images to illustrate the text I've seen in a log time.
Voted Yes and recommending for Featured Article.
0
LVL 18

Author Comment

by:LesterClayton
Thanks for the kind words - I like to show pictrures so that people can follow the important steps :)
0

Featured Post

Introducing the "443 Security Simplified" Podcast

This new podcast puts you inside the minds of leading white-hat hackers and security researchers. Hosts Marc Laliberte and Corey Nachreiner turn complex security concepts into easily understood and actionable insights on the latest cyber security headlines and trends.

Join & Write a Comment

With Secure Portal Encryption, the recipient is sent a link to their email address directing them to the email laundry delivery page. From there, the recipient will be required to enter a user name and password to enter the page. Once the recipient …
When cloud platforms entered the scene, users and companies jumped on board to take advantage of the many benefits, like the ability to work and connect with company information from various locations. What many didn't foresee was the increased risk…

Keep in touch with Experts Exchange

Tech news and trends delivered to your inbox every month