When running scripts in Windows PowerShell, an important security consideration that you should take into account is how to prevent unauthorized scripts from running on your system. By default, you can't run scripts in PowerShell. However, scripts provide an effective tool in Windows administration, so if you're using PowerShell, chances are you'll want to run scripts.
A PowerShell script is simply a text file with a .ps1 extension. The file contains one or more PowerShell statements that run when you call the script file at the console. PowerShell lets you control whether scripts can run, and if so, which scripts can run. To control script execution and help to protect your system, you need to
- Set PowerShell's execution policy.
- Create an X.509 certificate.
- Digitally sign your scripts.
If you take these precautions, only the scripts that you digitally sign will be permitted to run in PowerShell, thus helping to prevent malicious attacks on your system. Note that I assume you're already familiar with the PowerShell environment. If you're new to PowerShell, see the PowerShell 101 and PowerShell 201 series. For information about these series, go to "New to Scripting? Check Out These Series."
Setting the Execution Policy
The PowerShell execution policy controls whether you can run scripts and whether configuration files will be loaded when you start PowerShell. To set the execution policy, you must use the Set-ExecutionPolicy cmdlet to specify one of the following execution options:
- Restricted: PowerShell configuration files won't be loaded and scripts won't run. This is the most restrictive option and is the default. As a result, when you first install PowerShell, no unintended scripts will run or configurations will load. However, you can still run individual commands in the PowerShell console.
- AllSigned: All scripts and configuration files must be digitally signed by a trusted publisher. To sign a script, you must use a code-signing certificate. As you'll see later, you can create the certificate yourself.
- RemoteSigned: All scripts and configuration files downloaded from the Internet must be digitally signed. However, scripts on your local computer can run and local configuration files can be loaded without being digitally signed.
- Unrestricted: All scripts will run and all configuration files will be loaded. This is the least restrictive option and subsequently the riskiest.
As you can see, if you want to protect your system and still allow scripts to run and configuration files to load, you should set the execution policy to AllSigned. To set the policy, run the following command at the PowerShell command prompt:
You can verify PowerShell's current execution policy (always a good idea after changing the policy) by running the Get-ExecutionPolicy cmdlet (without any parameters). For more details about the Set-ExecutionPolicy and Get-ExecutionPolicy cmdlets, see the PowerShell Help files available for each cmdlet. For information about configuration (i.e., profile) files, see "Save Your PowerShell Code in Profile and Script Files" and the MSDN article "Windows PowerShell Profiles."
Creating an X.509 Certificate
After you set the execution policy to AllSigned, you must sign your files, which means you need a code-signing X.509 certificate. X.509 is a cryptography standard that defines the format for such security-related devices as public key certificates and certificate revocations lists. You can either purchase an X.509 certificate issued by a public certificate authority or you can create your own certificate authority and certificate. A full discussion of the X.509 standard and public certificate authorities is beyond the scope of this article. However, I'll explain how you can create your own local certificate authority and certificate.
To create a certificate authority and certificate on the local computer, you can use the Makecert utility that's included in the Microsoft .NET Framework SDK. (It's also available in Microsoft Visual Studio 2008 or Visual Studio 2005.) Note, however, that Makecert is meant for testing only. In a production environment, you should use a public key infrastructure (PKI) such as Microsoft Certificate Services to create certificate authorities and certificates.
As with any command-line utility, you can run Makecert at the PowerShell command prompt. For instance, when you create the certificate authority (which you must do before you create the certificate), you specify the name of the utility (Makecert) followed by the necessary options. In the following example, I create a certificate authority named PowerShell CA in the certificate store root:
makecert -n "CN=PowerShell CA" ` -eku 18.104.22.168.22.214.171.124.3 -r ` -sv PowerShellCA.pvk PowerShellCA.cer ` -ss Root -a sha1
This command includes a number of options. Table 1 provides a brief description of them.
You can find detailed information about these and other options in MSDN's .NET Framework Development Center. Note that, to run the command in this example, I added the path where the Makecert utility is located to the Path system environmental variable.
When you run this command, the Create Private Key Password dialog box appears (shown in Figure 1), prompting you to specify a password.
After you enter your password twice and click OK, the Enter Private Key Password dialog box appears (shown in Figure 2), prompting you for the password you just entered in the Create Private Key Password dialog box.
After you click OK, a Security Warning message box appears (similar to the one shown in Figure 3), warning you that you are about to install the PowerShell CA certificate authority.
After you click Yes, the Makecert utility creates the certificate authority in your local certificate store.
After the certificate authority has been created, the next step is to use the Makecert utility to create the actual certificate that will be used to sign your scripts. The following command creates a certificate named PowerShell Certificate, which is authorized by the PowerShell CA certificate authority:
makecert -n "CN=PowerShell Certificate" ` -eku 126.96.36.199.188.8.131.52.3 -pe ` -iv PowerShellCA.pvk ` -ic PowerShellCA.cer -ss My -a sha1
As with the previous command, this command includes a number of Makecert options. Table 1 also includes a description of these options. Note that, for the -ss option (which specifies the certificate store), I provided the value My rather than Root, as I had done when creating the certificate authority. The My value indicates that the certificate will be stored in the certificate store in the Personal folder of the Current User store. (The Current User store is used by default. You can also specify -sr LocalMachine to save the certificate to the Local Computer certificate store.)
When you run the Makecert command to create the certificate, you're once again prompted for a password. This is your private key password that you specified when you created the certificate authority. The certificate is then created in the current user’s Trusted Root Certification Authorities store.
You can view the certificate through the Microsoft Management Console (MMC) Certificates snap-in, as shown in Figure 4.
Notice that PowerShell Certificate is listed in the right pane of the MMC window. To view the details about the certificate, double-click it to open the Certificate dialog box. If the Certificates snap-in isn't available in an existing administrative tool, you'll have to add it to an MMC console. For information on how to do so, see the Microsoft article "How To Create Custom MMC Snap-in Tools Using Microsoft Management Console." After you verify that the certificate has been created, you can start signing your scripts.
Signing a PowerShell Script
Signing a script is a straightforward process. You use the Set-AuthenticodeSignature cmdlet and specify the script file to sign and the code-signing certificate to use when signing the file. For example, suppose you want to sign the C:\Audit\SecurityAudit.ps1 script file, which callout A in Listing 1 shows. (You can download SecurityAudit.ps1 by clicking the Download the Code Here button at the top of the page.) This script retrieves the most recent 20 events listed in the Security log.
The following statements first specify the script file and certificate, then run the Set-AuthenticodeSignature cmdlet:
$file = "C:\Audit\SecurityAudit.ps1" $cert = Get-ChildItem cert:\CurrentUser\My ` -CodeSigningCert Set-AuthenticodeSignature $file $cert
In the first statement, I assign the full filename as a string to the $file variable. In the second statement, I use the Get-ChildItem cmdlet to retrieve the code-signing certificate from the certificate store and assign it to the $cert variable. To retrieve the certificate, I specify as a path cert:\CurrentUser\My. The cert: prefix is the drive used to access the certificate store. This is followed by CurrentUser, which refers to the location within the certificate store. The My refers to the certificates within the Personal folder. When you use the Get-ChildItem cmdlet to retrieve the certificate, you should also include the -CodeSigningCert switch parameter to retrieve only the certificates that have code-signing authority.
If the My certificate store contains more than one code-signing certificate, the $cert variable will contain those certificates, in which case you must specify the desired certificate when you reference the $cert variable. One way to do this is to add the object index after the variable name. For example, you'd use $cert\[0\] to call the first code-signing certificate, $cert\[1\] to call the second one, and so on. However, if you know that there is only one code-signing certificate, you don't need to include the bracketed index reference.
After you have set the values of the $file and $cert variables, you're ready to sign your code. The third statement in the example uses the Set-AuthenticodeSignature cmdlet to sign the code. Notice that you provide the filename ($file) and certificate ($cert) as the two arguments to the cmdlet. When you run the command, the certificate is used to digitally sign the file. You can verify that a file has been signed by viewing its contents. At the end of the file, you'll find a block of commented code that is the digital signature. Callout B in Listing 1 shows what this signature might look like. After the file has been signed, you can run the script.
When you run a script that's been digitally signed, you'll be prompted to verify whether it's safe to run it. You can choose to never run the file, not run it this time, run it once, or always run it. If you chose to never run the file or always run the file, you won't be prompted again if you try to run the script. Otherwise, you'll be prompted the next time you try to run it.
Using a .pfx File to Sign a Script
If you use a private certificate to sign your files, it's still possible for a malicious program to use the certificate to sign a script, thus allowing an unwanted script to run. A way to help avoid this problem and provide even further protection for your system is to export your code-signing certificate to a .pfx file, then use that file to sign your script.
To export your certificate, open the Certificates snap-in and locate your certificate (refer back to Figure 4). Right-click the code-signing certificate, point to All Tasks, then point to Export. This launches the Certificate Export wizard. Follow the steps in the wizard to export the file. Be sure to export the private key along with the certificate and enable strong protection. You can also choose whether to include all certificates in the certification path, delete the private key, and export extended properties. You'll also need to provide a password and the file location. For these examples, I saved the file to C:\Audit\PS_Cert.pfx. After you export the certificate, be sure to delete it from the certificate store and be sure to store the .pfx file in a secure location.
After you run the wizard, you're ready to sign the file. As before, the first two statements should define the necessary variables, as in
$file = "C:\Audit\\SecurityAudit.ps1" $cert = Get-PfxCertificate C:\Audit\PS_Cert.pfx
In the first statement, I assign the script file's location to the $file variable. Next, I use the Get-PfxCertificate cmdlet to retrieve the .pfx file and save it in $cert.
When you run the second statement, you'll be prompted for a password. This is the password you specified when you exported the certificate to the file. As before, you use the Set-AuthenticodeSignature cmdlet to sign the file. When you run the cmdlet, specify the script and.pfx files, as in
Set-AuthenticodeSignature $file $cert
That's all there is to signing your file. As you can see, once you've created your certificate and, optionally, exported it to the .pfx file, it's a simple matter to sign the files, yet an effective way to help secure your system. As any administrator knows, you can never be too careful, especially when it comes to protecting your PowerShell scripts.