Windows PowerShellHeading Off Malicious Code

Don Jones

Remember back when Windows Vista was still in beta and there was buzz about a very early version of a new command-line shell code-named "Monad"? (This, of course, would eventually come to be known as Windows PowerShell.) At the time, there were many mainstream media reports about the "first Windows Vista virus." In

reality, the "virus" was just a proof-of-concept malware script that targeted "Monad." In order to run the script, "Monad" itself would have had to have been specially configured—the script wouldn't work under default settings. Moreover, by the time those reports surfaced, Microsoft had already announced that "Monad" would not ship as part of Windows Vista®. In short, the whole situation was much ado about nothing (or, at least, about very little).

As Windows PowerShellTM becomes more popular, though—it has already been downloaded more than a million times—the odds increase that someone will use it to create a malicious script. The ability to write a potentially damaging script in Windows PowerShell is a given; any admin tool—including Windows PowerShell, cmd.exe, and VBScript—can be used maliciously. So you can't just assume a given PS1 file is harmless.

Fortunately, Windows PowerShell is configured by default not to run any scripts, so that malicious script needs help from you if it is going to run. This month, I'd like to predict how this will probably happen. This isn't to make Windows PowerShell look bad—I think Microsoft has done a good job designing a scripting shell that avoids many of the risks. However, this is a discussion worth having just to help you prepare and be ready to head off this potential attack.

Secure by Default

It's worth noting that Windows PowerShell was the first language designed by Microsoft after the famous Trustworthy Computing initiative. Security guru Michael Howard (author of the book Writing Secure Code) was the Windows PowerShell team's "security buddy." He helped to make sure the code was written to be as secure as possible and, most importantly, that the shell was configured as securely as possible right out of the box.

First, let's quickly review the initial Windows PowerShell security settings. By default, the shell will not run files with a PS1 file name extension when you double-click on them. That extension is associated with Notepad. In fact, by default, the shell won't run scripts at all because of a built-in feature called the Execution Policy, which describes the conditions under which a script will run. It is set to Restricted out of the box, which prohibits all scripts from running and enables the shell only for interactive use. The Execution Policy can be changed using the Set-ExecutionPolicy cmdlet, or through a Group Policy administrative template (ADM file) supplied by Microsoft. (You can obtain the ADM file at Figure 1 shows the Execution Policies you can set.

Figure 1 Choosing a secure Execution Policy

Figure 1** Choosing a secure Execution Policy **(Click the image for a larger view)

The Execution Policy has only a few exceptions. Specifically, even when set to Restricted, it will allow the shell to import a few particular XML configuration files that are provided by Microsoft and installed along with the shell. These files are used to provide specific functionality, such as extensions to Microsoft® .NET Framework types and default formatting layouts for most .NET object types. Thus, these are files you definitely want the shell to load at startup.

While these files can and do contain executable code, they've been digitally signed by Microsoft. Tampering with them in any way renders the signature useless, and, if that happens, the shell will not import the files at startup. This design makes the files pretty secure against malware that might try to insert malicious code into them.

Of course, the Execution Policy, if left at Restricted, will also prevent your own Windows PowerShell profile scripts from being executed at startup. Windows PowerShell doesn't create a profile script by default, but it does search four specific locations for specific file names and, if it finds them, attempts to execute them every time the shell starts. (The documentation installed along with Windows PowerShell provides details about the folder and file names that are used for profile scripts.) The profile is the key to the exploit I'm going to discuss.

Modifying the Execution Policy

Cmdlet of the Month

The companion of Set-AuthenticodeSignature is Get-AuthenticodeSignature. This cmdlet is designed to examine a digitally signed script and give you details about the signature. Just point it to the file in question and you'll see not only if a file has been signed but whether the signature is intact, what certificate was used to sign the file, and so on. Besides working with Windows PowerShell scripts, this cmdlet also works with signed executables, like so:

PS C:\Program Files\Microsoft Office\Office12>
Get-AuthenticodeSignature excel.exe | Format-List *

By running this command, I can see that the executable was signed by Microsoft Corp. using a certificate issued by the Microsoft Code Signing CA.

Command results

Command results  (Click the image for a larger view)

Let me stress that under default conditions, it's very difficult—if not impossible—to get Windows PowerShell to execute any code, let alone malicious code. It's not until you modify the Execution Policy that malicious scripts even become possible. To be clear, this column is not a warning bell about security holes in Windows PowerShell; instead, it's meant to share some best practices for keeping your systems hardened.

The lowest Execution Policy is Unrestricted, which permits all scripts to run without restriction or question—essentially offering the same undesirable scenario you've had with VBScript and batch files for years. Set the shell to Unrestricted and you're just asking for a malicious script to come along and cause damage. If you do choose the Unrestricted setting, and a script comes along and clobbers you, be sure you can justify why you chose the Unrestricted setting, and be prepared to own up to your decision when you're explaining how a virus wiped out your environment!

To be fair, Windows PowerShell will still try to detect scripts downloaded from the Internet and warn you before running them, even when set to Unrestricted. But the point is that having an Execution Policy set to Unrestricted is a bad idea.

Signing Scripts

The most secure Execution Policy that permits scripts to be executed is AllSigned. This setting, as the name suggests, only executes scripts that carry an intact digital signature that was created using a trusted certificate (not just any old signature will do). Signing scripts requires you to acquire a Class III digital certificate—more specifically, a Microsoft Authenticode code-signing certificate. Such certificates can be obtained from the internal Public Key Infrastructure (PKI) of your company, if you have one, or purchased from commercial certificate authorities (CAs), such as CyberTrust, Thawte, and VeriSign.

If you want to know if you have any certificates installed on your machine that can be used for signing scripts, the following cmdlet will show you:

Get-ChildItem CERT: -recurse –codeSigningCert

After installing the certificate on your Windows® computer, you use the Set-AuthenticodeSignature cmdlet to create and apply a digital signature, which shows up as a series of gibberish-seeming comment lines at the end of the script. Some script editors may offer the option to apply a signature to a script file, including an opportunity to automatically sign scripts as you save them, which can be convenient.

AllSigned is the best Execution Policy to use in a production environment. While it doesn't prevent malicious scripts outright, it does ensure that a malicious script is signed and, therefore, you'd be able to track down the script's author (assuming your Windows computers are configured to trust only trustworthy CAs, but that topic is a bit beyond the scope of this column). Interestingly, Windows Script Host (WSH) 5.6 and later can be configured with a similar TrustPolicy setting that also requires digital signatures, but I've met few administrators who actually use this setting.

So here's a quick recap so far. With an Execution Policy set to Restricted, you're safe from malicious scripts, but you can't run beneficial ones, either. With your Execution Policy configured to AllSigned, the shell permits signed scripts, which is pretty safe since few malicious script authors want a verified, traceable identity applied to their work. The Unrestricted setting, on the other hand, is extremely unsafe and if you're using it, you can expect to get hit by some malicious script eventually. (Note that I don't regard the Unrestricted setting as a particular vulnerability because it doesn't pretend to be anything but insecure and if you're using this setting, you presumably know what you're in for.)

Sneaking in the Side Door

There's one more Execution Policy setting: RemoteSigned. This is the one I believe most administrators are using these days, simply because it's perceived as more secure than Unrestricted and less of a hassle than AllSigned. RemoteSigned does not require a signature for local scripts. PS1 files residing on your local drives will run without being signed. Remote scripts—primarily those downloaded from the Internet using Internet Explorer® or Outlook® (these apps mark downloaded files with a special flag)—won't run unless they're signed.

The RemoteSigned setting can give you a false sense of security, however. To begin with, it's easy to download remote scripts without having the special flag applied. Non-Microsoft browsers, for example, typically don't set this flag, nor do most non-Microsoft e-mail clients. It's important to note that without that flag, Windows PowerShell treats downloaded scripts as local ones—meaning a signature won't be required. Still, I don't regard this as a significant vulnerability. You have to actually download the script, open Windows PowerShell, and manually execute the script for it to run. It's a bit tough to trick someone into performing all these steps, and administrators—generally the only users on a network who have Windows PowerShell installed—should know better.

RemoteSigned does, however, offer a significant "side door" that malware can sneak through. Remember those Windows PowerShell profile scripts? If they exist—whether they were created by you or by a piece of malware—they'll execute every time Windows PowerShell runs. And under the RemoteSigned Execution Policy, your profile scripts—which are local—don't need to be signed.

Here's the scenario:

  1. Some piece of malware makes it onto your system and either creates a shell profile script or inserts malicious code into an existing profile script. Malware typically runs under your logged-on user account, which generally has permission to modify your profile script.
  2. You open Windows PowerShell, not realizing your profile script has been created or modified to include malicious code. The code executes, and the damage is done. The damage is worse if you're in the habit of opening Windows PowerShell under Administrator credentials—a common practice because when you're using the shell, you need administrative privileges to perform whatever tasks you're using the shell for.

The profile, then, presents a way for arbitrary and malicious code to execute without your knowledge, and the RemoteSigned Execution Policy allows it to happen.

Protecting the Profile

There is only one good way to protect your profile: Use the AllSigned Execution Policy. Under AllSigned, even your profile scripts must be digitally signed; otherwise, Windows PowerShell won't execute them at startup. And if you have signed your profile scripts, malicious modifications to them will break their digital signatures, leaving them unable to run—in fact, Windows PowerShell will alert you to the problem at startup. AllSigned really is the only safe Execution Policy setting for production environments that need to allow scripts to run.

There is another approach, but it's more complicated and less trustworthy. You can create blank profile script files for each of the four files Windows PowerShell looks for. Use a new user account (I'll call it "Profile Editor") to create these files and set the NTFS permissions of the files so that only the Profile Editor account can modify them. Other accounts can have read-only access.

Now, don't ever log on to your computer using the Profile Editor account except to edit your profile. With this approach, your normal user account (or accounts) won't be able to modify your profile scripts. And any malware that runs while you're logged on with a normal account won't be able to modify the scripts, either. What if malware happens to run while you're logged on as Profile Editor? Well, considering you'll be logged on as Profile Editor to edit your profile scripts anyway, you should notice the changes.

You can roll your own safety net, too, by creating an all-users profile script that's controlled with tight file permissions, as I just described. Within that script, write code that uses the Get-AuthenticodeSignature cmdlet to examine the digital signatures on the other profiles the shell looks for. This essentially lets you require signatures on profile scripts, without requiring them for other scripts.

The AllSigned Execution Policy, however, is a much more thorough way to protect your profile. My recommendation is that any computer connected to your network should have an Execution Policy of Restricted, preferably applied by Group Policy. This overrides any local setting and ensures that new domain computers are automatically set to disallow scripts. Computers on which scripts must be permitted should have an alternate Group Policy that sets the AllSigned Execution Policy. You must digitally sign all scripts, but you can rest easy knowing that only trusted scripts from identifiable authors are executing in your environment.

Don Jones is the Lead Scripting Guru for SAPIEN Technologies and coauthor of Windows PowerShell: TFM (SAPIEN Press, 2007). You can contact him at

© 2008 Microsoft Corporation and CMP Media, LLC. All rights reserved; reproduction in part or in whole without permission is prohibited.