At Microsoft, we invest an enormous amount of time and energy managing world-class cloud services and incredibly large enterprise networks. Security is critical for all of these – so what might surprise you is that we also invest enormous amounts of time and energy trying to break into those services. This is called Red Teaming – taking highly specialized groups of security experts and having them adopt the role of sophisticated adversaries.

In the last several releases of Windows, we’ve been working hard to make the platform much more powerful for administrators, developers, and power users alike. The only problem is – the red teams are catching on.

A Note about Assume Breach
In this post, the assumption is that an attacker has already compromised (breached) a system through a malicious phishing email, security flaw in a custom website implementation, or similar attack.

When these security flaws are in software, they are found and patched. But we always assume the attackers will find some way to get in – even if only through a user being tricked into installing a malicious application on their computer.\

As with any occupation, job satisfaction for attackers (either funded by the company under attack or otherwise) plays an important role in influencing attacker behaviour. After all, who wants to extend their compromise of a system using error prone and hard-to-write C++ programs, when you can accomplish the same thing with an elegant and powerful scripting language like PowerShell?

In this post, we’ll discuss some important advances we’ve made in scripting security and protection in the preview versions of PowerShell version 5, and Windows 10.

PowerShell ♥ the Blue Team

When you take an assume-breach mindset, you have to assume that an attacker is already on your system. But then you’re left with questions: What did they do? What systems did they connect to? Was any dynamic code invoked, and what was it?

PowerShell version 5 (included in Windows 10, and also available for earlier operating systems through the Windows Management Framework) has made significant strides in making sure that the Blue Team has the information it needs to answer these questions.

KB 3000850 for PowerShell v4 on Windows 8.1 also includes many of these features, as called out below.

Over-the-shoulder transcription

One of the quickest ways to get a summary of what’s happening in a PowerShell session is to look over the shoulder of the person typing. You see their commands, the output of those commands, and all is well. Or it’s not, but at least you’ll know.

PowerShell versions 4 and prior include support for over-the-shoulder transcription through the Start-Transcript command. However, setting up ubiquitous transcription of PowerShell sessions is complex and error-prone. You need to include the command in the system startup profile of every system, and also need to add significant amounts of auditing to flag attackers that attempt to disable transcription.

A secondary issue is that transcription was only supported in the interactive PowerShell console. Transcription of remoting sessions were not supported, nor was transcription in non-console hosts such as the PowerShell ISE.

In PowerShell version 5 and KB 3000850, Start-Transcript now emits structured objects when you start a transcript (the Path property is useful), and has added much more useful information to its header:

The filename now includes the computer that generated the transcript, a ‘hash breaker’ to prevent transcript collisions, and increased granularity in the transcript start time. While PowerShell v4 and below let you control the output path, you were then forced to properly randomize the transcript filename yourself. To improve this situation, we’ve added the –OutputDirectory parameter to Start-Transcript.

In the header content, the “Username” and “RunAs User” will normally be the same. If you’ve enabled impersonation on a constrained PowerShell remoting endpoint (i.e.: PowerShell Just Enough Administration), the “Username” field represents the connected user while the “RunAs User” represents the account being impersonated.

When it comes to transcript content, PowerShell now transcribes (what it can) of console commands that manipulate the console buffer directly, and can now be enabled in hosts such as the PowerShell ISE.

If you want to more directly associate commands with their output for potential later analysis, use the –IncludeInvocationHeader parameter. This adds an additional header for each command that is invoked:

To enable automatic transcription, enable the ‘Turn on PowerShell Transcription’ feature in Group Policy through Windows Components -> Administrative Templates -> Windows PowerShell. For automation, the configuration settings are stored under HKLM:\Software\Policies\Microsoft\Windows\PowerShell\Transcription. The following PowerShell functions let you enable and disable the system-wide transcription policies.

The OutputDirectory setting lets you collect transcripts to a central location (UNC path) for later review. If you implement this policy, ensure that access to the central share is limited to prevent users from reading previously-written transcripts. The following PowerShell script creates a “Transcripts” SMB share on a server that follows this best practice.

## Grant everyone else Write and ReadAttributes. This prevents users from listing## transcripts from other machines on the domain.$everyone=[System.Security.Principal.NTAccount]“Everyone”$permission=$everyone,“Write,ReadAttributes”,“ObjectInherit,ContainerInherit”,“None”,“Allow”$accessRule=New-ObjectSystem.Security.AccessControl.FileSystemAccessRule$permission$acl.AddAccessRule($accessRule)

## Create the SMB Share, granting Everyone the right to read and write files. Specific## actions will actually be enforced by the ACL on the file folder.New-SmbShare-NameTranscripts-Pathc:\Transcripts-ChangeAccessEveryone

Deep script block logging

A PowerShell “script block” is the base level of executable code in PowerShell. It might represent a command typed interactively in the PowerShell console, supplied through the command line (“PowerShell –Command <…>”), or wrapped in a function, script, workflow, or the like.

In addition to over-the-shoulder style transcription, PowerShell v5 and KB 3000850 introduces deep script block logging. When you enable script block logging, PowerShell records the content of all script blocks that it processes. If a script block uses dynamic code generation (i.e.: $command=“‘Hello World'”; Invoke-Expression$command), PowerShell will log the invocation of this generated script block as well. This provides complete insight into the script-based activity on a system – including scripts or applications that leverage dynamic code generation in an attempt to evade detection.

As with transcription support, this deep script block logging applies to any application that hosts the PowerShell engine – the command line shell, ISE, or custom host.

To enable automatic transcription, enable the ‘Turn on PowerShell Script Block Logging’ feature in Group Policy through Windows Components -> Administrative Templates -> Windows PowerShell. For automation, the configuration settings are stored under HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging. By default, PowerShell only logs scripts blocks the first time they are used. If you select ‘Log script block invocation start / stop events’, PowerShell also logs start and stop events for every time a script block is invoked. This latter setting can generate an extremely high volume of events, so should be enabled with caution.

The following PowerShell functions let you enable and disable the system-wide script block logging policies.

Most companies only realize the need to enable script block logging after it is too late. To provide some recourse in this situation, PowerShell automatically logs script blocks when they have content often used by malicious scripts. This automatic script block logging is not intended to replace antivirus or full script block logging – it only serves as a record of last resort.

When script block logging is enabled, PowerShell will log the following events to the Microsoft-Windows-PowerShell/Operational log:

EventId

4104 / 0x1008

Channel

Operational

Level

Verbose

Opcode

Create

Task

CommandStart

Keyword

Runspace

Message

Creating Scriptblock text (%1 of %2):

%3

ScriptBlock ID: %4

The text embedded in the message is the text of the script block compiled. The ScriptBlock ID is a GUID retained for the life of the script block.

Note: Some script block texts (i.e.: Get-ChildItem) might not truly be representative of its underlying functionality if that command was generated through PowerShell’s dynamic keyword mechanism or an overridden function. For both of these situations, the original dynamic keyword definition (or malicious function definition) will be logged.

When script block invocation logging is enabled, PowerShell also writes begin and end event markers:

EventId

Start: 4105 / 0x1009
(Complete: 4106 / 0x100A)

Channel

Operational

Level

Verbose

Opcode

Open (/ Close)

Task

CommandStart (/ CommandStop)

Keyword

Runspace

Message

Started (/ Completed) invocation of ScriptBlock ID: %1

Runspace ID: %2

The ID is the GUID representing the script block (that can be correlated with event ID 4104), and the Runspace ID represents the runspace this script block was run in.

Given that it represents the content of all PowerShell script invoked on a system, these events may contain sensitive data. To limit the information disclosure risk when script block logging is enabled, see Protected Event Logging.

Percent signs in the invocation message represent structured ETW properties. While they are replaced with the actual values in the message text, a more robust way to access them is to retrieve the message with the Get-WinEvent cmdlet, and then use the Properties array of the message.

Here’s an example of how this functionality can help unwrap a malicious attempt to encrypt and obfuscate a script:

If the script block length exceeds what ETW is capable of holding in a single event, Windows PowerShell breaks the script into multiple parts. Here is sample code to recombine a script from its log messages:

As with all logging systems that have a limited retention buffer (i.e.: ETW logs), one attack against this infrastructure is to flood the log with spurious events to hide earlier evidence. To protect yourself from this attack, ensure that you have some form of event log collection set up (i.e.: Windows Event Forwarding, http://www.nsa.gov/ia/_files/app/Spotting_the_Adversary_with_Windows_Event_Log_Monitoring.pdf) to move event logs off of the computer as soon as possible.

Cryptographic Message Syntax (CMS) encryption and decryption cmdlets

PowerShell version 5 and KB 3000850 introduces support for protection of content using the Cryptographic Message Syntax (CMS) format. These cmdlets support encryption and decryption of content using the IETF standard format for cryptographically protecting messages as documented by RFC5652.

The CMS encryption standard implements public key cryptography, where the keys used to encrypt content (the public key) and the keys used to decrypt content (the private key) are different.

Your public key can be shared widely, and is not sensitive data. If any content is encrypted with this public key, only your private key can decrypt it. For more information about Public Key Cryptography, see: http://en.wikipedia.org/wiki/Public-key_cryptography.

To be recognized in Windows PowerShell, encryption certificates require a unique key usage identifier (EKU) to identify them as data encryption certificates (like the identifiers for ‘Code Signing’, ‘Encrypted Mail’).

Here is an example of creating a certificate that is good for Document Encryption:

(Change the text in Subject to your name, email, or other identifier), and put in a file (i.e.: DocumentEncryption.inf):

Any parameter of type CMSMessageRecipient supports identifiers in the following formats:

·An actual certificate (as retrieved from the certificate provider)

·Path to the a file containing the certificate

·Path to a directory containing the certificate

·Thumbprint of the certificate (used to look in the certificate store)

·Subject name of the certificate (used to look in the certificate store)

To view document encryption certificates in the certificate provider, you can use the –DocumentEncryptionCert dynamic parameter for Get-ChildItem (dir):

58 [Cert:\currentuser\my]
>> dir -DocumentEncryptionCert

Interoperability of CMS Content

Because the CMS format is an IETF standard, PowerShell supports the decryption of content generated by other conforming tools, and the content it generates can be decrypted by other conforming tools.

One of the more popular implementations to support the CMS message format is the OpenSSL library and command-line toolchain. The primary challenge when exchanging data with the OpenSSL library comes from the OpenSSL assumption that the content is contained within an email message body in the P7M format. Fortunately, these text-based headers are relatively easy to add and remove.

The following PowerShell commands demonstrate using OpenSSL and PowerShell to encrypt and decrypt content generated by the other application.

## Install the OpenSSL packageInstall-PackageOpenSSL.Light

## OpenSSL requires certificates in the PEM format. To create this,## export the Windows certificate in PFX format, and ensure that## the PFX is protected by a password (rather than account) as## OpenSSL doesn’t support group-protected PFX files&“C:\Program Files\OpenSSL\bin\openssl.exe”pkcs12-inC:\temp\cert.pfx-outc:\temp\cert.pem-nodes

Secure code generation APIs

Whenever you write code that may be subjected to attacker-controlled input, code injection vulnerabilities are among the most dangerous type of bug. A good example of code that may be subjected to attacker-controlled input are functions that you expose in a constrained PowerShell runspace. If an attacker can exploit a code injection vulnerability in one of those functions, they can execute code as though it were part of the function itself. That code would not be subject to the restrictions that you’ve applied to the constrained runspace.

Almost every language can be subject to code injection vulnerabilities if used incorrectly. In SQL, this is called “SQL Injection”. In web sites, this is called “Cross site scripting”. In CGI applications, shell scripts, or tools that invoke system commands – this is called “Command injection”.

In PowerShell, the most common source of code injection vulnerabilities comes from including attacker-controlled input in a string that you submit to the Invoke-Expression command. For example:

functionGet-MyAcl{

param($Path)

Invoke-Expression“Get-Acl $Path“}

If $Path contains input such as “; Write-Host Pwnd”, the attacker can now execute the Write-Host cmdlet (or much worse!) as well.

The Invoke-Expression cmdlet should almost always be avoided, as PowerShell (like other languages) has many features that take its place more securely.

## Invoke a static command

Get-Acl-Pathc:\temp\file.txt

## Supply a dynamic parameter value

## with a variable reference

$paramValue=“c:\temp\file.txt”

Get-Acl-Path$paramValue

## Supply both a dynamic parameter name and

## value through ‘splatting’

$parameters= @{ Path =“c:\temp\file.txt” }

Get-Acl@parameters

## Supply a dynamic command name, parameter name,

## and parameter value through the invocation

## operator and splatting

$commandName=“Get-Acl”

$parameters= @{ Path =“c:\temp\file.txt” }

&$commandName@parameters

If you are ever truly required to generate PowerShell scripts after making all attempts to avoid it, PowerShell version 5 and KB 3000850introduces APIs to support secure generation of scripts that may contain attacker input.

If you are placing attacker-controlled input within a string (i.e.: for a command argument), ensure that you place it within a single-quoted string. Then, use EscapeSingleQuotedStringContent on the content itself. This ensures that single quotes (or their equivalents – for there are several) in the attacker input are escaped properly.

For example:

$attackerInput=“Hello’World”

$escapedAttackerInput=“‘”+

[Management.Automation.Language.CodeGeneration]::

EscapeSingleQuotedStringContent($attackerInput) +“‘”

$newScript=“Write-Host $escapedAttackerInput“

Invoke-Expression$newScript

Safe escaping of content to be included within block comments, format strings, or variable names is also supported.

Constrained PowerShell

When a system is sensitive, one of the most powerful ways to limit the damage an attack can have is to reduce the capabilities of that attack. Windows’ security controls come in many forms – creating a hierarchy of protections that incrementally add value.

Control

Benefit

Impact Without Control

Limitations

Antivirus / Antimalware

Can limit the execution of malware known to the AV industry.

Attacker can write and run any code, custom C++ applications, internet tools, etc.

Can be disabled by administrators. AV signatures can be evaded if the attacker is capable of recompiling or modifying an application.

Applocker in Deny Mode

Can limit the execution of malware known to your organization.

Attacker can write and run any code, custom C++ applications, etc., as long as they aren’t well known attack tools or exploits.

Can be disabled by administrators. Only blocks known evil / undesirable malware, can be bypassed with only minor application changes.

Applocker in Allow Mode

Can prevent the execution of unknown / unapproved applications.

Attacker can write arbitrary custom applicatons, as long as they are not detected by AV or Applocker Deny rules.

Can be disabled by administrators. Attacker can still leverage in-box tools like VBScript, Office macros, HTA applications, local web pages, PowerShell, etc.

The strongest form of protection is when a system employs AppLocker in ‘Allow Mode’, where only specific known applications are allowed to run.

Prior to PowerShell version 5, a limitation of AppLocker’s ‘Allow Mode’ was that interactive PowerShell input was not subject to this policy. While Allow Mode might prevent unknown PowerShell scripts from running, it would not prevent the equivalent commands entered at an interactive prompt.

In version 5, PowerShell now reduces its functionality to “Constrained Mode” for both interactive input and user-authored scripts when it detects that PowerShell scripts have an ‘Allow Mode’ policy applied to them. Constrained PowerShell limits the language mode to Constrained Language (as described in about_Language_Modes), a mode first introduced for Windows RT.

Constrained Language doesn’t limit the capability of the core PowerShell language – familiar techniques such as variables, loops, and functions are all supported. It does, however, limit the extended language features that can lead to unverifiable code execution such as direct .NET scripting, invocation of Win32 APIs via the Add-Type cmdlet, and interaction with COM objects.

Scripts that are allowed by the AppLocker policy (for example: signed by the enterprise’s trusted code signing certificate, or in a trusted directory) are not subject to Constrained Language. They have access to the extended capabilities of the PowerShell language disallowed by Constrained Language. This includes unverifiable extensions such as .NET scripting, and invocation of Win32 APIs.

Here’s an example PowerShell command that lets you experiment with AppLocker in ‘Allow Mode’ for all scripts (i.e.: blocking all VBScripts, batch files, and PowerShell scripts by default), and then allows only PowerShell scripts from c:\trusted to run.

Beware – if users can add or edit files in c:\trusted, then this policy offers no protection. Users in that situation can simply put scripts in that directory to bypass the policy. Also, if your AppLocker policy doesn’t similarly limit executables, then this policy offers no protection. Users in that situation can simply run an executable to bypass the policy.

In order to enforce its policies, AppLocker requires the AppIDSvc service to be running. When enabling a policy, be sure to set the service to Auto Start.

As mentioned previously, Constrained PowerShell layers on top of the Windows permissions model. Because of that, it (like AppLocker) should be applied to regular user accounts and not system administrators. Administrator accounts can bypass the policy by simply changing or disabling it.

One concern when increasing the amount of logging on a system is the danger that logged content may contain sensitive data. For example, if you log the content of every PowerShell script that was run, there is the possibility that a script may contain credentials or other sensitive data.

If an attacker later compromises a machine that has logged this data, it may provide them with additional information with which to extend their reach.

To prevent this dilemma, Windows 10 introduces Protected Event Logging. Protected Event Logging lets participating applications encrypt sensitive data as they write it to the event log. You can then decrypt and process these logs once you’ve moved them to a more secure and centralized log collector.

One common technique to move event logs to a more secure and centralized log collector is built in to Windows: Windows Event Forwarding. A great document on setting up Windows Event Forwarding is available from the NSA: “Spotting the Adversary with Windows Event Log Monitoring”. Other options are System Center Operations Manager, or commercially available Security Information and Event Management (SIEM) systems.

In Windows 10, PowerShell is the only application that participates in Protected Event Logging.

Protected Event Logging protects event log content through the IETF Cryptographic Message Syntax (CMS) standard. The CMS encryption standard implements public key cryptography, where the keys used to encrypt content (the public key) and the keys used to decrypt content (the private key) are separate.

Your public key can be shared widely, and is not sensitive data. If any content is encrypted with this public key, only your private key can decrypt it. For more information about Public Key Cryptography, see: http://en.wikipedia.org/wiki/Public-key_cryptography.

When you implement a protected event logging policy, you deploy a public key to all machines that have event log data you want to protect. You retain the corresponding private key to post-process the event logs at a more secure location such as a central event log collector, or SIEM aggregator.

To enable Protected Event Logging, enable the ‘Enable Protected Event Logging’ feature in Group Policy through Windows Components -> Administrative Templates -> Event Logging. This setting requires an encryption certificate, which you can provide in one of several forms:

The content of a base-64 encoded X.509 certificate (for example, as offered by the ‘Export’ option in Certificate Manager)

The thumbprint of a certificate that can be found in the Local Machine certificate store (usually deployed by PKI infrastructure)

The full path to a certificate (can be local, or a remote share)

The path to a directory containing a certificate or certificates (can be local, or a remote share)

The subject name of a certificate that can be found in the Local Machine certificate store (usually deployed by PKI infrastructure)

The resulting certificate must have ‘Document Encryption’ as an enhanced key usage (1.3.6.1.4.1.311.80.1), as well as either Data Encipherment or Key Encipherment key usages enabled.

You can also use the following PowerShell function to enable protected event logging:

While the Group Policy template for Protected Event Logging only exists in Windows 10, PowerShell version 5 and PowerShell in KB3000850 supports protected event logging if the settings are configured manually.

If an application cannot properly resolve the encryption certificate during logging, it will log a warning message into its event log channel, and then continue to log the data without event log protection.

When configuring the encryption certificate for deployment, ensure that it doesn’t include the private key. If the certificate includes the private key, then it can also be used to decrypt the protected event log content.

The following commands show how to determine if a Document Encryption certificate on a node has been deployed with a private key:

To post-process the content of protected event log messages, use the PowerShell Unprotect-CmsMessage cmdlet and Cryptographic Message Syntax (CMS) encryption and decryption cmdlets.

For example, the following PowerShell commands automatically decrypt encrypted event log messages, provided that an appropriate decryption certificate (i.e.: one that has the private key) is installed on the machine:

To retain the structure of the actual event log entry (while just decrypting the Message field), use the –IncludeContext parameter:

One thing I noticed which on the face of it is totally obvious but I thought hmm that could be easily overlooked is that PowerShell Version 2 is an installed feature.
Running: PowerShell version 2 to drop into V2 means an easy bypass of the logging that’s someone would expect.
Not sure if there is a way to remove the ability for a user to change to PowerShell version 2 without uninstalling the feature?

If I use Allow mode in SRP C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 does not start. If I run powershell.exe I always get error:
C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1 : Cannot dot-source this command because it was def
ferent language mode. To invoke this command without importing its contents, omit the ‘.’ operator.
At line:1 char:1
+ . ‘C:\Windows\System32\WindowsPowerShell\v1.0\profile.ps1’
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (:) [profile.ps1], NotSupportedException
+ FullyQualifiedErrorId : DotSourceNotSupported,profile.ps1

But Profile.ps1 is located in C:\Windows\System32\WindowsPowerShell\v1.0 folder. This folder is allowed by SRP path rules. So why Profile.ps1 does not start automaticaly? And why Profile.ps1 starts in Constrained mode?

Yes, you’re right. All other AppLocker rules work as desired. Signed or by path rules do not work for scripts in PowerShell (W7 32/64-bit computers). On W10 computers work the same AppLocker rules properly.

@Andrew – Our suggestions for dealing with double-hop are the security best practices presentation linked to above. CredSSP is not – as you point out, it donates your username and password to a machine that may be compromised.

@Martin – While your comment is not related to this post, it's still valid 🙂 Can submit a feature request to connect.microsoft.com/powershell?

@asdf – We've exposed the maximum that can be exposed to fully untrusted input. It support constants and a few other things. One example where exposing multiplication is a memory and CPU denial of service via string multiplication. For example:

"Pwnd"*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2*2

For the data language specifically (where you're not concerned about security), you can do this:

The command 'Get-Process' is not allowed in restricted language mode or a Data section."

At line:1 char:1

+ $sb.CheckRestrictedLanguage($noneAllowed, $noneAllowed, $false)

+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

+ CategoryInfo : NotSpecified: (:) [], MethodInvocationException

Lee

3 years ago

Martin T. Nielsen

How about support for .NET Tasks and other such wonderful multithreading capabilities already present in .NET? A built-in Invoke-Parallel command perhaps?

3 years ago

concerned admin

This is probably not the right place. It is great the Powershell is getting more security features. There is a problem in System Center Configuration Manager (SCCM) where SCCM bloggers are advising to disable signing for Powershell scripts run by the agent. It just seems the signing Powershell scripts is too much of a burden to use even though it could provide a big security benefit. Maybe SCCM needs to provide the Powershell signing management as a built-in feature. For instance, it would sign any script saved in compliance settings and push the certificate trust of the script publisher to clients.

Is double hop via passing credentials in the clear with CredSSP being adressed? I'm not yet a powershell pro but it seems there's got to be a better way to adress double hop.

3 years ago

asdf

That's not one better for certain applications. e.g.

I have an interactive script that needs to receive integers from the console as coordinates. I want to enter integers like "123" and expressions like "4 * 3" which I want to simplify to "12". The DATA { } sublanguage would be perfect there.

@asdf – I'll do you one better 🙂 We're working on an API to traverse the AST of a script block, and return the values that are safe to evaluate. No dynamic evaluation or PowerShell runspaces are required – this is entirely accomplished through static analysis of the AST. A version of this is in the latest WMF preview, this is what it currently looks like: