Chapter 18. Security and Script Signing

Introduction

Security plays two important roles in PowerShell. The first role is the security of PowerShell itself. Scripting languages have long been a vehicle of email-based malware on Windows, so PowerShell's security features have been carefully designed to thwart this danger. The second role is the set of security-related tasks you are likely to encounter when working with your computer: script signing, certificates, and credentials, just to name a few.

When it comes to talking about security in the scripting and command-line world, a great deal of folklore and superstition clouds the picture. One of the most common misconceptions is that scripting languages and command-line shells somehow lets users bypass the security protections of the Windows graphical user interface.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 4 hours ago

    somehow lets users --> somehow let users

Add a comment

The Windows security model protects resources—not the way you get to them. That is because programs that you run, in effect, are you. If you can do it, so can a program. If a program can do it, then you can do it without having to use that program. For example, consider the act of changing critical data in the Windows Registry. If you use the Windows Registry Editor graphical user interface, it provides an error message when you attempt to perform an operation that you do not have permission for, as shown in Figure 18.1, “Error message from the Windows Registry Editor”.

The Registry Editor provides this error message because it is unable to delete that key, not because it wanted to prevent you from doing it. Windows itself protects the registry keys, not the programs you use to access them.

Figure 18.1. Error message from the Windows Registry Editor

Error message from the Windows Registry Editor

Likewise, PowerShell provides an error message when you attempt to perform an operation that you do not have permission for. Not because PowerShell contains extra security checks for that operation, but because it is also simply unable to perform the operation:

PS > New-Item "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run\New"
New-Item : Requested registry access is not allowed.
At line:1 char:9
+ New-Item <<<< "HKLM:\Software\Microsoft\Windows\CurrentVersion\Run\New"

While perhaps clear after explanation, this misunderstanding often gets used as a reason to prevent users from running command shells or scripting languages altogether.

Enable Scripting Through an Execution Policy

Problem

PowerShell provides an error message when you try to run a script:

PS > .\Test.ps1
File C:\temp\test.ps1 cannot be loaded because the execution of scripts is disa
bled on this system. Please see "get-help about_signing" for more details.
At line:1 char:10
+ .\Test.ps1 <<<<

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    Or, if you try to import a module:

    PS> Import-Module PSDiagnostics

    Import-Module : File C:Windowssystem32WindowsPowerShellv1.0Modules PSDiagnosticsPSDiagnostics.psm1 cannot be loaded because the execution of scripts is disabled on this system. Please see "get-help about_signing" for more details.

    At line:1 char:14 + import-module <<<< PSDiagnostics

Add a comment

Solution

To prevent this error message, use the Set-ExecutionPolicy cmdlet to change the PowerShell execution policy to one of the policies that allow scripts to run:

Set-ExecutionPolicy RemoteSigned

Discussion

As normally configured, PowerShell operates strictly as an interactive shell. By disabling the execution of scripts by default, PowerShell prevents malicious PowerShell scripts from affecting users who have PowerShell installed, but who may never have used (or even heard of!) PowerShell.

You (as a reader of this book and PowerShell user) are not part of that target audience. You will want to configure PowerShell to run under one of the following five execution policies:

Restricted

PowerShell operates as an interactive shell only. Attempting to run a script generates an error message. This is PowerShell's default execution policy.

AllSigned

PowerShell only runs scripts that contain a digital signature. When you attempt to run a script signed by a publisher that PowerShell hasn't seen before, PowerShell asks whether you trust that publisher to run scripts on your system.

RemoteSigned (recommended)

PowerShell runs most scripts without prompting, but requires that scripts that originate from the Internet contain a digital signature. As in AllSigned mode, PowerShell asks whether you trust that publisher to run scripts on your system when you run a script signed by a publisher it hasn't seen before. PowerShell considers a script to have come from the Internet when it has been downloaded to your computer by a popular communications programs such as Internet Explorer, Outlook, or Messenger.

Unrestricted

PowerShell does not require a digital signature on any script, but (like Windows Explorer) warns you when a script originates from the Internet.

Bypass

PowerShell places the responsibility of security validation entirely upon the user.

When it comes to evaluating script signatures, always remember that a signed script does not mean a safe script! The signature on a script gives you a way to verify who the script came from, but not that you can trust its author to run commands on your system. You need to make that decision for yourself, which is why PowerShell asks you.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 5 hours ago

    What about the Undefined value? ("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.") Maybe a new recipe for a problem "How can I remove the currently assigned execution policy?"?

Add a comment

Run the Set-ExecutionPolicy cmdlet to configure the system's execution policy. It supports three scopes:

Process

Impacts the current session, and any that it launches. This scope modifies the PSExecutionPolicy environment variable, and is also supported through the -ExecutionPolicy parameter to PowerShell.exe.

CurrentUser

Modifies the execution policy for the current user, and stores its value in the HKEY_CURRENT_USER hive of the Windows registry.

LocalMachine

Modifies the execution policy for the entire machine, and stores its value in the HKEY_LOCAL_MACHINE hive of the Windows registry. Modifying the execution policy at this scope requires that you launch PowerShell with Administrator privileges. If you want to configure your execution policy on Windows Vista or later, right-click the Windows PowerShell link for the option to launch PowerShell as Administrator.

If you specify the value of Undefined for the execution policy at a specific scope, PowerShell removes any execution policy you previously defined for that scope.

2 comments

  1. Aleksandar Nikolic Posted 12 days and 5 hours ago

    enviornment variable --> environment variable

  2. Aleksandar Nikolic Posted 9 days and 4 hours ago

    64-bit OS has 32-bit version of the PowerShell too, and 32-bit version uses different registry path to store the value of ExecutionPolicy—SOFTWAREWow6432NodeMicrosoftPowerShell1ShellIdsMicrosoft.PowerShell. It might be a good idea to mention that. People are usually confused that they need to set up execution policy for both versions, if they want to run scripts in 32-bit PowerShell as well.

Add a comment

Alternatively, you may directly modify the registry key that PowerShell uses to store its execution policy. For the Currentuser and LocalMachine scopes, this is the ExecutionPolicy property under the registry path SOFTWARE\Microsoft\PowerShell\1\ShellIds\Microsoft.PowerShell.

In an enterprise setting, PowerShell also lets you override this local preference through Group Policy. For more information about PowerShell's Group Policy support, see the section called “Manage PowerShell Security in an Enterprise”.

Execution policies are not user restrictions

It is easy to understand the power of an execution policy to prevent scripts from running, but administrators often forget to consider from whom. They might think that enforcing an AllSigned policy is a way to prevent the user from running non-approved applications, when it is designed as a way to prevent the attacker from running scripts that the user doesn't approve. This misconception is often wrongly reinforced by the location of the ExecutionPolicy configuration key in PowerShell version one – in a registry location that only machine administrators have access to.

System-wide PowerShell execution policies cannot prevent the user from doing something they want to do. That job is left to the Windows Account Model, which is designed as a security boundary. It controls what a user can do: what files they can access, what registry keys they can access, and more. PowerShell is a user-mode application, and is therefore (by the Windows security model) completely under the user's control.

Instead, Execution Policies are a user-focused feature like seatbelts or helmets. It's best to keep them on, but you always have the option to take them off. PowerShell's installer sets the execution policy to Restricted as a safe default for the vast majority of Windows users that will never run a PowerShell script in their life. A system administrator might set the execution policy to AllSigned because they want to define it as a best practice, or let non-technical users run a subset of safe scripts.

At any time, the user can decide otherwise. They can type the commands by hand, paste the script into their PowerShell prompt, or any of a countless number of other work arounds. These are all direct results of one of Windows' core security principles: you have complete control over any application you are running. PowerShell version two makes this reality much more transparent through its fine-grained execution policy scopes.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 4 hours ago

    Windows' core security tenet --> "Windows' core security principle", maybe?

Add a comment

At its core, execution policy scopes let administrators and users tailor their safety harness. Jane might be fluent and technical (and opt for a RemoteSigned execution policy), while Bob (another user of the same machine with different security preferences) can still get the benefits of an AllSigned default execution policy. In addition, agents or automation tools can invoke PowerShell commands without having to modify the permanent state of the system.

See Also

Disable Warnings for UNC Paths

Problem

PowerShell warns you when it tries to load a script from an Intranet (UNC) path.

Solution

Enable Internet Explorer's UncAsIntranet setting, or add the UNC path to the list of trusted sites. Example 18.1, “Adding a server to the list of trusted hosts” adds server to the list of trusted sites.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 4 hours ago

    IMO, "the Trusted Sites zone" is more appropriate term here than "the list of trusted hosts" to avoid a confusion with the TrustedHosts list setting in WS-Management.

Add a comment

Example 18.1. Adding a server to the list of trusted hosts

$path = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Internet Settings\" +
    "ZoneMap\Domains\server"
New-Item -Path $path | New-ItemProperty -Name File -PropertyType DWORD -Value 2

Discussion

When using an execution policy that detects Internet-based scripts, you may want to stop PowerShell from treating those scripts as remote.

In an enterprise setting, PowerShell sometimes warns of the dangers of Internet-based scripts even if they are located only on a network share. To remove this warning, first, ensure they have not actually been downloaded from the Internet. Right-click on the file from Windows Explorer, select Properties, and then click Unblock.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 4 hours ago

    the internet --> the Internet

Add a comment

If unblocking the file does not resolve the issue (or is not an option), your machine has likely been configured to restrict access to network shares. This is common with Internet Explorer's Enhanced Security Configuration mode. To prevent this message, add the path of the network share to Internet Explorer's Intranet or Trusted Sites zone. For more information on managing Internet Explorer's zone mappings, see the section called “Add a Site to an Internet Explorer Security Zone”.

If you are using an Unrestricted execution policy and still want to get rid of this warning for remote files, you can use the Bypass execution policy to bypass PowerShell's security features entirely. For more information about execution policies, see the section called “Enable Scripting Through an Execution Policy”.

Sign a PowerShell Script, Module, or Formatting File

Problem

You want to sign a PowerShell script, module, or formatting file so that it may be run on systems that have their execution policy set to require signed scripts.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    IMO, "Module" is missing in a title.

Add a comment

Solution

To sign the script with your standard code-signing certificate, use the Set-AuthenticodeSignature cmdlet:

2 comments

  1. Aleksandar Nikolic Posted 8 days and 19 hours ago

    What is correct? "code-signing" or "code signing"? This page contains both ("code-signing" (10 times) and "code signing" (6 times)).

  2. Lee Holmes Posted 7 days and 18 hours ago

    I've changed it to be two "code-signing certificate", although there are instances where it isn't hyphenated due to a different context (such as "certificates that support code signing".

Add a comment

$cert = @(Get-ChildItem cert:\CurrentUser\My -CodeSigning)[0]
Set-AuthenticodeSignature file.ps1 $cert

1 comment

  1. Aleksandar Nikolic Posted 8 days and 19 hours ago

    -CodeSigning --> -CodeSigningCert

Add a comment

Alternatively, you may also use other traditional applications (such as signtool.exe) to sign PowerShell .ps1, .psm1, .psd1, and .ps1xml files.

2 comments

  1. Aleksandar Nikolic Posted 9 days and 4 hours ago

    Alternatively,you --> Alternatively, you

  2. Aleksandar Nikolic Posted 9 days and 3 hours ago

    .psm1 files should be mentioned too. Module files need to be signed, if we want to import them on systems that have their execution policy set to require signed scripts.

Add a comment

Discussion

Signing a script or formatting file provides you and your customers with two primary benefits: publisher identification and file integrity. When you sign a script, module, or formatting file, PowerShell appends your digital signature to the end of that file. This signature verifies that the file came from you and also ensures that nobody can tamper with the content in the file without detection. If you try to load a file that has been tampered with, PowerShell provides the following error message:

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    IMO, every time when you talk about "signing a script and formatting file", module need to be added too-"signing a script, module and formatting file".

Add a comment

File C:\temp\test.ps1 cannot be loaded. The contents of file C:\temp\test.ps1
may have been tampered because the hash of the file does not match the hash
stored in the digital signature. The script will not execute on the system. Please
see "get-help about_signing" for more details.
At line:1 char:10
+ .\test.ps1 <<<<

When it comes to the signing of scripts, modules, and formatting files, PowerShell participates in the standard Windows Authenticode infrastructure. Because of that, techniques you may already know for signing files and working with their signatures continue to work with PowerShell scripts and formatting files. While the Set-AuthenticodeSignature cmdlet is primarily designed to support scripts and formatting files, it also supports DLLs and other standard Windows executable file types.

To sign a file, the Set-AuthenticodeSignature cmdlet requires that you provide it with a valid code-signing certificate. Most certification authorities provide Authenticode code-signing certificates for a fee. By using an Authenticode code-signing certificate from a reputable certification authority (such as VeriSign or Thawte), you can be sure that all users will be able to verify the signature on your script. Some online services offer extremely cheap code-signing certificates, but be aware that many machines may be unable to verify the digital signatures created by those certificates.

Note

You can still gain many of the benefits of code signing on your own computers by generating your own code-signing certificate. While other computers will not be able to recognize the signature, it still provides tamper-protection on your own computer. For more information about this approach, see the section called “Program: Create a Self-Signed Certificate”.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    of code signing on --> of code-signing on

Add a comment

The –TimeStampServer parameter lets you sign your script or formatting file in a way that makes the signature on your script or formatting file valid even after your codesigning certificate expires.

For more information about the Set-AuthenticodeSignature cmdlet, type Get-Help Set-AuthenticodeSignature.

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    cmdlet,type --> cmdlet, type

Add a comment

Program: Create a Self-Signed Certificate

Discussion

It is possible to benefit from the tamper-protection features of signed scripts without having to pay for an official code-signing certificate. You do this by creating a self-signed certificate. Scripts signed with a self-signed certificate will not be recognized as valid on other computers, but you can still sign scripts on your own computer.

When Example 18.2, “New-SelfSignedCertificate.ps1” runs, it prompts you for a password. Windows uses this password to prevent malicious programs from automatically signing files on your behalf.

Example 18.2. New-SelfSignedCertificate.ps1

##############################################################################

if(-not (Get-Command makecert.exe -ErrorAction SilentlyContinue))
{
    $errorMessage = "Could not find makecert.exe. " +
        "This tool is available as part of Visual Studio, or the Windows SDK."

    Write-Error $errorMessage
    return
}

$keyPath = Join-Path ([IO.Path]::GetTempPath()) "root.pvk"

makecert -n "CN=PowerShell Local Certificate Root" -a sha1 `
    -eku 1.3.6.1.5.5.7.3.3 -r -sv $keyPath root.cer `
    -ss Root -sr localMachine

makecert -pe -n "CN=PowerShell User" -ss MY -a sha1 `
    -eku 1.3.6.1.5.5.7.3.3 -iv $keyPath -ic root.cer

Remove-Item $keyPath

Get-ChildItem cert:\currentuser\my -codesign | 
    Where-Object { $_.Subject -match "PowerShell User" }
      

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    makecert.exe is also included in the Microsoft .NET Framework SDK.

Add a comment


For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.

Manage PowerShell Security in an Enterprise

Problem

You want to control PowerShell's security features in an enterprise setting.

Solution

To manage PowerShell's security features enterprise-wide:

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    enterprisewide --> enterprise-wide

Add a comment

  • Apply PowerShell's Group Policy templates to control PowerShell's execution policy through Group Policy.

  • Deploy Microsoft Certificate Services to automatically generate Authenticode code-signing certificates for domain accounts.

Discussion

Apply PowerShell's Group Policy templates

The administrative templates for Windows PowerShell let you override the machine's local execution policy preference at both the machine and per-user level. To obtain the PowerShell administrative templates, visit http://www.microsoft.com/downloads and search for "Administrative templates for Windows PowerShell."

Note

Although Group Policy settings override local preferences, PowerShell's execution policy should not be considered a security measure that protects the system from the user. It is a security measure that helps prevent untrusted scripts from running on the system. As mentioned in the section called “Enable Scripting Through an Execution Policy”, PowerShell is only a vehicle that allows users to do what they already have the Windows permissions to do.

Once you install the administrative templates for Windows PowerShell, launch the Group Policy Object Editor MMC snapin. Right-click Administrative Templates and then select Add/Remove Administrative Templates. You will find the administrative template in the installation location you chose when you installed the administrative templates for Windows PowerShell. Once added, the Group Policy Editor MMC snapin provides PowerShell as option under its Administrative Templates node, as shown in Figure 18.2, “PowerShell Group Policy configuration”.

Figure 18.2. PowerShell Group Policy configuration

PowerShell Group Policy configuration

The default state is Not Configured. In this state, PowerShell takes its execution policy from the machine's local preference (as described in the section called “Enable Scripting Through an Execution Policy”. If you change the state to one of the Enabled options (or Disabled), PowerShell uses this configuration instead of the machine's local preference.

Note

PowerShell respects these Group Policy settings no matter what. This includes settings that the machine's administrator may consider to reduce security—such as an Unrestricted group policy overriding an AllSigned local preference.

Per-user Group Policy settings override the machine's local preference, while per-machine Group Policy settings override per-user settings.

Deploy Microsoft Certificate services

Although outside the scope of this book, Microsoft Certificate Services lets you automatically deploy code-signing certificates to any or all domain users. This provides a significant benefit, as it helps protect users from accidental or malicious script tampering.

For an introduction to this topic, visit http://technet.microsoft.com and search for "Enterprise Design for Certificate Services." For more information about script signing, see the section called “Sign a PowerShell Script, Module, or Formatting File”.

Block Scripts by Publisher, Path, or Hash

Problem

In addition to PowerShell's execution policy, you want to block scripts by their publisher, location, or similarity to a specific script.

Solution

Create new Software Restriction Policy rules to enforce these requirements.

Discussion

While not common, you may sometimes want to prevent PowerShell from running scripts signed by specific publishers, from a certain path, or with specific content. For all execution policies except for Bypass, PowerShell lets you configure this through the computer's software restriction policies.

To configure these software restriction policies, launch the Local Security Policy MMC snapin listed in the Administrative Tools group of the Start menu. Expand the Software Restriction Policies node, right-click Additional Rules, and then create the desired rules: certificate rules, path rule, or hash rules.

Note

In Windows 7, the PowerShell module for the AppLocker feature makes managing software restriction policies immensely easier. For more information, search the Internet for Applocker PowerShell.

Certificate rules let you configure certain certificates that PowerShell will never trust. Path rules let you define system paths that allow or disallow execution of PowerShell scripts from certain paths. Hash rules let you block specific scripts from execution if they are the same as the script you used to generate the rule.

Figure 18.3, “Adding a new certificate rule” demonstrates how to add a new Certificate Rule.

Figure 18.3. Adding a new certificate rule

Adding a new certificate rule

Browse to the certificate that represents the publisher you want to block, and then click OK to block that publisher.

Rather than block specific certificates, you can also create certificate policy that allows only certificates from a centrally administered whitelist. To do this, select either Allow only all administrators to manage Trusted Publishers or Allow only enterprise administrators to manage Trusted Publishers from the Trusted Publishers Management dialog.

Verify the Digital Signature of a PowerShell Script

Problem

You want to verify the digital signature of a PowerShell script or formatting file.

Solution

To validate the signature of a script or formatting file, use the Get-AuthenticodeSignature cmdlet:

PS > Get-AuthenticodeSignature .\test.ps1

    Directory: C:\temp

SignerCertificate                         Status     Path
-----------------                         ------     ----
FD48FAA9281A657DBD089B5A008FAFE61D3B32FD  Valid      test.ps1

Discussion

The Get-AuthenticodeSignature cmdlet gets the Authenticode signature from a file. This can be a PowerShell script or formatting file, but the cmdlet also supports DLLs and other Windows standard executable file types.

By default, PowerShell displays the signature in a format that summarizes the certificate and its status. For more information about the signature, use the Format-List cmdlet, as shown in Example 18.3, “PowerShell displaying detailed information about an Authenticode signature”.

Example 18.3. PowerShell displaying detailed information about an Authenticode signature

PS > Get-AuthenticodeSignature .\test.ps1 | Format-List

SignerCertificate      : [Subject]
                           CN=PowerShell User

                         [Issuer]
                           CN=PowerShell Local Certificate Root

                         [Serial Number]
                           454D75B8A18FBDB445D8FCEC4942085C

                         [Not Before]
                           4/22/2007 12:32:37 AM

                         [Not After]
                           12/31/2039 3:59:59 PM

                         [Thumbprint]
                           FD48FAA9281A657DBD089B5A008FAFE61D3B32FD

TimeStamperCertificate :
Status                 : Valid
StatusMessage          : Signature verified.
Path                   : C:\temp\test.ps1

For more information about the Get-AuthenticodeSignature cmdlet, type Get-Help Get-AuthenticodeSignature.

Securely Handle Sensitive Information

Problem

You want to request sensitive information from the user, but want to do this as securely as possible.

Solution

To securely handle sensitive information, store it in a SecureString whenever possible. The Read-Host cmdlet (with the –AsSecureString parameter) lets you prompt the user for (and handle) sensitive information by returning the user's response as a SecureString:

PS > $secureInput = Read-Host -AsSecureString "Enter your private key"
Enter your private key: 
PS > $secureInput
System.Security.SecureString

Discussion

When you use any string in the .NET Framework (and therefore PowerShell), it retains that string so that it can efficiently reuse it later. Unlike most .NET data, unused strings persist even after you finish using them. When this data is in memory, there is always the chance that it could get captured in a crash dump, or swapped to disk in a paging operation. Because some data (such as passwords and other confidential information) may be sensitive, the .NET Framework includes the SecureString class: a container for text data that the framework encrypts when it stores it in memory. Code that needs to interact with the plain-text data inside a SecureString does so as securely as possible.

2 comments

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    PowerShell),it -- > PowerShell) ,it

  2. Aleksandar Nikolic Posted 9 days and 3 hours ago

    Sorry.

    PowerShell),it -- > PowerShell), it

Add a comment

When a cmdlet author asks you for sensitive data (for example, an encryption key), the best practice is to designate that parameter as a SecureString to help keep your information confidential. You can provide the parameter with a SecureString variable as input, or the host prompts you for the SecureString if you do not provide one. PowerShell also supports two cmdlets (ConvertTo-SecureString and ConvertFrom-SecureString) that let you securely persist this data to disk. For more information about securely storing information on disk, see the section called “Securely Store Credentials on Disk”.

Note

Credentials are a common source of sensitive information. See the section called “Securely Request Usernames and Passwords” for information on how to securely manage credentials in PowerShell.

By default, the SecureString cmdlets use Windows' Data Protection API (DPAPI) when they convert your SecureString to and from its text representation. The key it uses to encrypt your data is based on your Windows logon credentials, so only you can decrypt the data that you've encrypted. If you want the exported data to work on another system or separate user account, you can use the cmdlet options that let you provide an explicit key. PowerShell treats this sensitive data as an opaque blob—and so should you.

However, there are many instances when you may want to automatically provide the SecureString input to a cmdlet rather than have the host prompt you for it. In these situations, the ideal solution is to use the ConvertTo-SecureString cmdlet to import a previously exported SecureString from disk. This retains the confidentiality of your data and still lets you automate the input.

If the data is highly dynamic (for example, coming from a CSV), then the ConvertTo-SecureString cmdlet supports an –AsPlainText parameter:

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    CSV),then --> CSV), then

Add a comment

$secureString = ConvertTo-SecureString "Kinda Secret" -AsPlainText-Force

Since you've already provided plain-text input in this case, placing this data in a SecureString no longer provides a security benefit. To prevent a false sense of security, the cmdlet requires the -Force parameter to convert plain-text data into a SecureString.

Once you have data in a SecureString, you may want to access its plain-text representation. PowerShell doesn't provide a direct way to do this, as that defeats the purpose of a SecureString. If you still want to convert a SecureString to plain text, you have two options:

1 comment

  1. Aleksandar Nikolic Posted 9 days and 3 hours ago

    doest't provide --> doesn't provide

Add a comment

  1. Use the GetNetworkCredential() method of the PsCredential class

    $secureString = Read-Host -AsSecureString
    $temporaryCredential = New-Object `
        System.Management.Automation.PsCredential "TempUser",$secureString
    $unsecureString = $temporaryCredential.GetNetworkCredential().Password
  1. Use the .NET Framework's Marshal class

    $secureString = Read-Host -AsSecureString
    $unsecureString = [Runtime.InteropServices.Marshal]::PtrToStringAuto(
        [Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureString))

Securely Request Usernames and Passwords

Problem

Your script requires that users provide it with a username and password, but you want to do this as securely as possible.

Solution

To request a credential from the user, use the Get-Credential cmdlet:

$credential = Get-Credential

Discussion

The Get-Credential cmdlet reads credentials from the user as securely as possible and ensures that the user's password remains highly protected the entire time. For an example of using the Get-Credential cmdlet effectively in a script, see the section called “Program: Start a Process As Another User”.

Once you have the username and password, you can pass that information around to any other command that accepts a PowerShell credential object without worrying about disclosing sensitive information. If a command doesn't accept a PowerShell credential object (but does support a SecureString for its sensitive information), the resulting PsCredential object provides a Username property that returns the username in the credential and a Password property that returns a SecureString containing the user's password.

Unfortunately, not everything that requires credentials can accept either a PowerShell credential or SecureString. If you need to provide a credential to one of these commands or API calls, the PsCredential object provides a GetNetworkCredential() method to convert the PowerShell credential to a less secure NetworkCredential object. Once you've converted the credential to a NetworkCredential, the UserName and Password properties provide unencrypted access to the username and password from the original credential. Many network-related classes in the .NET Framework support the NetworkCredential class directly.

Note

The NetworkCredential class is less secure than the PsCredential class because it stores the user's password in plain text. For more information about the security implications of storing sensitive information in plain text, see the section called “Securely Handle Sensitive Information”.

If a frequently run script requires credentials, you might consider caching those credentials in memory to improve the usability of that script. For example, in the region of the script that calls the Get-Credential cmdlet, you can instead use the techniques shown by Example 18.4, “Caching credentials in memory to improve usability”.

Example 18.4. Caching credentials in memory to improve usability

$credential = $null
if(Test-Path Variable:\Lee.Holmes.CommonScript.CachedCredential)
{
    $credential = ${GLOBAL:Lee.Holmes.CommonScript.CachedCredential}
}

${GLOBAL:Lee.Holmes.CommonScript.CachedCredential} =
   Get-Credential $credential

$credential = ${GLOBAL:Lee.Holmes.CommonScript.CachedCredential}

The script prompts the user for their credentials the first time they call it but uses the cached credentials for subsequent calls. If your command is part of a PowerShell module, you can avoid storing the information in a global variable. For more information about this technique, see the section called “Write Commands that Maintain State”.

To cache these credentials on disk (to support un-attended operation), see the section called “Securely Store Credentials on Disk”.

For more information about the Get-Credential cmdlet, type Get-Help Get-Credential.

Program: Start a Process As Another User

Discussion

If your script requires user credentials, PowerShell offers the PsCredential object. This lets you securely store those credentials, or pass them to other commands that accept PowerShell credentials. When you write a script that accepts credentials, consider letting the user to supply either a username or a preexisting credential. This is the model followed by the Get-Credential cmdlet, and provides an intuitive user experience. Example 18.5, “Start-ProcessAsUser.ps1” demonstrates a useful approach to support this model. As the framework for this demonstration, the script lets you start a process as another user. While this scenario addressed by this specific script is fully handled by the Start-Process cmdlet, it provides a useful framework for discussion.

Example 18.5. Start-ProcessAsUser.ps1

##############################################################################

param(
  $credential = (Get-Credential),
  [string] $process = $(throw "Please specify a process to start."),
  [string] $arguments = ""
  )

if($credential -is "String")
{
    $credential = Get-Credential $credential
}

if(-not ($credential -is "System.Management.Automation.PsCredential"))
{
    return
}

$startInfo = New-Object Diagnostics.ProcessStartInfo
$startInfo.Filename = $process
$startInfo.Arguments = $arguments

if(($credential.Username -eq "$ENV:Username") -or
    ($credential.Username -eq "\$ENV:Username"))
{
    $startInfo.Verb = "runas"
}
else
{
    $startInfo.UserName = $credential.Username
    $startInfo.Password = $credential.Password
    $startInfo.UseShellExecute = $false
}

[Diagnostics.Process]::Start($startInfo)
      

For a version of this script that lets you invoke PowerShell commands in an elevated session and easily interact with the results, see the section called “Program: Run a Temporarily Elevated Command”.

For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.

Program: Run a Temporarily Elevated Command

Discussion

One popular feature of many Unix-like operating systems is the sudo command: a feature that lets you invoke commands as another user without switching context.

This is a common desire in Windows Vista and above, where User Access Control (UAC) means that most interactive sessions do not have their Administrator privileges enabled. Enabling these privileges is often a clumsy task, requiring that you launch a new instance of PowerShell with the "Run as Administrator" option enabled.

Example 18.6, “Invoke-ElevatedCommand.ps1” resolves many of these issues by launching an administrative shell for you, and letting it participate in a regular (non-elevated) PowerShell pipeline.

To do this, it first streams all of your input into a richly-structured CliXml file on disk. It invokes the elevated command, and stores its results into another richly-structure CliXml file on disk. Finally, it imports the structured data from disk, and removes the temporary files.

Example 18.6. Invoke-ElevatedCommand.ps1

##############################################################################

<#

.SYNOPSIS
Runs the provided script block under an elevated instance of PowerShell as
through it were a member of a regular pipeline.

.EXAMPLE
PS >Get-Process | Invoke-ElevatedCommand.ps1 {
    $input | Where-Object { $_.Handles -gt 500 } } | Sort Handles

#>

param(
    [Parameter(Mandatory = $true)]
    [ScriptBlock] $Scriptblock,
    
    [Parameter(ValueFromPipeline = $true)]
    $InputObject,
    
    [switch] $EnableProfile
  )

begin
{
    $inputItems = New-Object System.Collections.ArrayList
}

process
{
    $null = $inputItems.Add($inputObject)
}

end
{
    $outputFile = [IO.Path]::GetTempFileName()
    $inputFile = [IO.Path]::GetTempFileName()
    
    $inputItems.ToArray() | Export-CliXml -Depth 1 $inputFile

    $commandLine = ""
    if(-not $EnableProfile) { $commandLine += "-NoProfile " }

    $commandString = "Set-Location '$($pwd.Path)'; " +
        "`$output = Import-CliXml '$inputFile' | " +
        "& {" + $scriptblock.ToString() + "} 2>&1; " +
        "Export-CliXml -Depth 1 -In `$output '$outputFile'"

    $commandBytes = [System.Text.Encoding]::Unicode.GetBytes($commandString)
    $encodedCommand = [Convert]::ToBase64String($commandBytes)
    $commandLine += "-EncodedCommand $encodedCommand"

    $process = Start-Process -FilePath (Get-Command powershell).Definition `
        -ArgumentList $commandLine -Verb RunAs `
        -WindowStyle Hidden `
        -Passthru
    $process.WaitForExit()

    if((Get-Item $outputFile).Length -gt 0)
    {
        Import-CliXml $outputFile
    }

    Remove-Item $outputFile
    Remove-Item $inputFile
}
      

For more information about the CliXml commands, see the section called “Easily Import and Export Your Structured Data”. For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.

Securely Store Credentials on Disk

Problem

Your script performs an operation that requires credentials, but you don't want it to require user interaction when it runs.

Solution

To securely store the credential's password to disk so that your script can load it automatically, use the ConvertFrom-SecureString and ConvertTo-SecureString cmdlets.

Save the credential's password to disk

The first step for storing a password on disk is usually a manual one. Given a credential that you've stored in the $credential variable, you can safely export its password to <currentScript>.ps1.credential using the following command. Replace <CurrentScript> with the name of the script that will be loading it: a useful convention, but not required.

PS > $credPath = Join-Path (Split-Path $profile) <CurrentScript>.ps1.credential
PS > $credential.Password | ConvertFrom-SecureString | Set-Content $credPath

Recreate the credential from the password stored on disk

In the script that you want to run automatically, add the following commands:

$credPath = Join-Path (Split-Path $profile) <CurrentScript>.ps1.credential
$password = Get-Content $credPath | ConvertTo-SecureString
$credential = New-Object System.Management.Automation.PsCredential `
    "CachedUser",$password

These commands create a new credential object (for the CachedUser user) and store that object in the $credential variable.

Discussion

When reading the solution, you might at first be wary of storing a password on disk. While it is natural (and prudent) to be cautious of littering your hard drive with sensitive information, the ConvertFrom-SecureString cmdlet encrypts this data using Windows' standard Data Protection API. This ensures that only your user account can properly decrypt its contents.

While keeping a password secure is an important security feature, you may sometimes want to store a password (or other sensitive information) on disk so that other accounts have access to it anyway. This is often the case with scripts run by service accounts or scripts designed to be transferred between computers. The ConvertFrom-SecureString and ConvertTo-SecureString cmdlets support this by letting you to specify an encryption key.

Note

When used with a hard coded encryption key, this technique no longer acts as a security measure. If a user can access to the content of your automated script, they have access to the encryption key. If the user has access to the encryption key, they have access to the data you were trying to protect.

Although the solution stores the password in the directory that contains your profile, you could also load it from the same location as your script. To learn how to load it from the same location as your script, see the section called “Find Your Script's Location”.

For more information about the ConvertTo-SecureString and ConvertFrom-SecureString cmdlets, type Get-Help ConvertTo-SecureString or Get-Help ConvertFrom-SecureString.

Access User and Machine Certificates

Problem

You want to retrieve information about certificates for the current user or local machine.

Solution

To browse and retrieve certificates on the local machine, use PowerShell's certificate drive. This drive is created by the certificate provider, as shown in Example 18.7, “Exploring certificates in the certificate provider”.

Example 18.7. Exploring certificates in the certificate provider

PS > Set-Location cert:\CurrentUserPS > $cert = Get-ChildItem -Rec -CodeSign
PS > $cert | Format-List

Subject      : CN=PowerShell User
Issuer       : CN=PowerShell Local Certificate Root
Thumbprint   : FD48FAA9281A657DBD089B5A008FAFE61D3B32FD
FriendlyName :
NotBefore    : 4/22/2007 12:32:37 AM
NotAfter     : 12/31/2039 3:59:59 PM
Extensions   : {System.Security.Cryptography.Oid, System.Security.Cryptogr
               aphy.Oid}

2 comments

  1. Aleksandar Nikolic Posted 8 days and 20 hours ago

    This is probably just an error on this page, and in a manuscript these commands are in two lines:

    PS > Set-Location cert:CurrentUser

    PS > $cert = Get-ChildItem -Rec -CodeSign

    The full name of a parameter is preferable: -Recurse and -CodeSigningCert.

  2. Lee Holmes Posted 7 days and 18 hours ago

    Yes, it's caused by the feedback system treating the back-slashes in the path as escape characters. It's correct in the manuscript.

Add a comment


Discussion

The certificate drive provides a useful way to navigate and view certificates for the current user or local machine. For example, if your execution policy requires the use of digital signatures, the following command tells you which publishers are trusted to run scripts on your system:

Get-ChildItem cert:\CurrentUser\TrustedPublisher

The certificate provider is probably most commonly used to select a code-signing certificate for the Set-AuthenticodeSignature cmdlet. The following command selects the "best" code-signing certificate: the one that expires last.

$certificates = Get-ChildItem Cert:\CurrentUser\My -CodeSign
$signingCert = @($certificates | Sort -Desc NotAfter)[0]

1 comment

  1. Aleksandar Nikolic Posted 8 days and 20 hours ago

    -CodeSign --> -CodeSigningCert

    -Desc --> -Descending

Add a comment

In this -CodeSign parameter lets you search for certificates in the certificate store that support code signing. To search for certificates used for other purposes, see the section called “Program: Search the Certificate Store”.

1 comment

  1. Aleksandar Nikolic Posted 8 days and 20 hours ago

    -CodeSign --> -CodeSigningCert

Add a comment

Although the certificate provider is useful for browsing and retrieving information from the computer's certificate stores, it does not let you add or remove items from these locations. If you want to manage certificates in the certificate store, the System.Security.Cryptography.X509Certificates.X509Store class (and other related classes from the System.Security.Cryptography.X509Certificates namespace) from the .NET Framework support that functionality. For an example of this approach, see the section called “Add and Remove Certificates”.

For more information about the certificate provider, type Get-Help Certificate.

Program: Search the Certificate Store

Discussion

One useful feature of the certificate provider is that it supports a –CodeSign parameter that lets you search for certificates in the certificate store that support code signing.

2 comments

  1. Aleksandar Nikolic Posted 8 days and 20 hours ago

    -CodeSign --> -CodeSigningCert

  2. Johannes Rössel Posted 8 days and 18 hours ago

    If it's not already mentioned previously in the book then this may be a nice point to tell the reader about dynamic parameters, i. e. those that are added by a specific provider. In fact, it took me some time to even find the concept again in the included help. It's probably very confusing if you want to look up the parameter only to find that it doesn't exist in the cmdlet documentation.

Add a comment

This parameter is called a dynamic parameter: one that has been added by a provider to a core PowerShell cmdlet. You can discover the dynamic parameters for a provider by navigating to that provider, and then reviewing the output of Get-Command -Syntax. For example:

PS > Set-Location cert:PS > Get-Command Get-ChildItem -Syntax
Get-ChildItem [[-Path] <String[]>] [[-Filter] <String>] (...) [-CodeSigningCert]

In addition to reading the output of Get-Command, the help topic for the provider often describes the dynamic parameters it supports. For a list of the provider help topics, type Get-Help -Category Provider.

Code-signing certificates are not the only kind of certificates, however; other frequently used certificate types are Encrypting File System, Client Authentication, and more.

Example 18.8, “Search-CertificateStore.ps1” lets you search the certificate provider for certificates that support a given Enhanced Key Usage (EKU).

Example 18.8. Search-CertificateStore.ps1

##############################################################################

param(
  $ekuName = $(throw "Please specify the friendly name of an " +
                     "Enhanced Key Usage (such as 'Code Signing'")
  )

foreach($cert in Get-ChildItem cert:\CurrentUser\My)
{
    foreach($extension in $cert.Extensions) 
    { 
        foreach($certEku in $extension.EnhancedKeyUsages) 
        { 
            if($certEku.FriendlyName -eq $ekuName) 
            { 
                $cert 
            } 
        } 
    } 
}
      

For more information about running scripts, see the section called “Run Programs, Scripts, and Existing Tools”.

Add and Remove Certificates

Problem

You want to add and remove certificates from the certificate store.

Solution

Use the certificate store APIs from the .NET Framework, as shown in Example 18.9, “Adding and removing certificates”.

Example 18.9. Adding and removing certificates

## Removing a certificate
$cert = Get-ChildItem cert:\currentuser\TrustedPublisher\<thumbprint>
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store `
    "TrustedPublisher","CurrentUser"
$store.Open("ReadWrite")
$store.Remove($cert)
$store.Close()

$cert = Get-PfxCertificate <path_to_certificate>
$store = New-Object System.Security.Cryptography.X509Certificates.X509Store `
    "TrustedPublisher","CurrentUser"
$store.Open("ReadWrite")
$store.Add($cert)
$store.Close()

Discussion

The certificate drive provides a useful way to navigate and view certificates for the current user or local machine. For example, if your execution policy requires the use of digital signatures, the following command tells you which publishers are trusted to run scripts on your system:

Get-ChildItem cert:\CurrentUser\TrustedPublisher

The certificate provider is ultimately a read-only view of your certificates, however. After using the certificate provider to retrieve a certificate, you can then use the .NET APIs to remove it from the certificate store permanently.

Likewise, the Get-PfxCertificate cmdlet lets you review a certificate from a file that contains it, but does not let you install it into the certificate store permanently. The .NET APIs are also the way to import the certificate for good.

For more information about retrieving certificates from the certificate provider, see the section called “Access User and Machine Certificates”. For more information about working with classes from the .NET Framework, see the section called “Work with .NET Objects”.

Manage Security Descriptors in SDDL Form

Problem

You want to work with a security identifier in Security Descriptor Definition Language (SDDL) form.

2 comments

  1. Aleksandar Nikolic Posted 8 days and 19 hours ago

    An example of the actual SDDL form and a short explanation would be nice. ("The format is a null-terminated string with tokens to indicate each of the four main components of a security descriptor: owner (O:), primary group (G:), DACL (D:), and SACL (S:).")

  2. Lee Holmes Posted 7 days and 18 hours ago

    I thought about that, but it's pretty chewy. I left it to the MSDN link in the rest of the recipe.

Add a comment

Solution

Use the System.Security.AccessControl.CommonSecurityDescriptor class from the .NET Framework, as shown by Example 18.10, “Automating security configuration of PowerShell Remoting”.

Example 18.10. Automating security configuration of PowerShell Remoting

## Get the SID for the "PowerShell Remoting Users" group
$account = New-Object Security.Principal.NTAccount "PowerShell Remoting Users"
$sid = $account.Translate([Security.Principal.SecurityIdentifier]).Value

$config = Get-PsSessionConfiguration Microsoft.PowerShell
$existingSddl = $config.SecurityDescriptorSddl

$arguments = $false,$false,$existingSddl
$mapper = New-Object Security.AccessControl.CommonSecurityDescriptor $arguments

$mapper.DiscretionaryAcl.AddAccess("Allow",$sid,268435456,"None","None")

$newSddl = $mapper.GetSddlForm("All")

Set-PSSessionConfiguration Microsoft.PowerShell -SecurityDescriptorSddl $newSddl


Discussion

Security descriptors are often shown (or requested) in SDDL form. The SDDL form of a security descriptor is cryptic, highly-specific, and plain text. All of these aspects make it difficult to work with reliably, so you can use the System.Security.AccessControl.CommonSecurityDescriptor class from the .NET Framework to do most of the gritty work for you.

For more information about the SDDL format, see http://msdn.microsoft.com/en-us/library/aa379570%28VS.85%29.aspx. For an example of this in action, see the section called “Configure User Permissions for Remoting”.

You must sign in or register before commenting
*
*
*
*
*

Atom Icon Comments on this page or Comments on the whole book.