PowerShell Security: PowerShell Downgrade Attacks

Previously, we talked about some great PowerShell security features like transcription which are available since PowerShell version 5 and how they provide great visibility over what PowerShell code is ran and how they expose what trajectory the attacker has taken. 
But… this makes older versions of PowerShell much more attractive for attackers…

Lee Holmes from the PowerShell product group already tackled this here, but I wanted to revisit the topic.


So why are these older versions of PowerShell interesting to attackers?

First of all, when forcing PowerShell to run using its PowerShell 2.0 engine (read: downgrade), none of the advanced security features (such as transcription) are available, since the older .NET Framework v2.0 is loaded.
It’s as simple as typing

powershell.exe -Version 2.0 -Command {<scriptblock>} -ExecutionPolicy <ExecutionPolicy>

Second of all, when looking at the attack vector, all machines running Windows 7 and above will have at least PowerShell 2.0. This easily targets a lot of machines, a lot…
By default, with Windows 10, the .NET Framework 2.0 is not included and makes it (by default) not possible to run PowerShell 5.x using it’s legacy PowerShell 2.0 engine.

Third, knowing that attackers love PowerShell for it’s ability to provide easy access to many platform components/features such as the Win32 APIs, registry access, enumeration of files, interaction with services, examination of processes, download of files from internet, obfuscate code, event log access, ability to inject code and/or reflectively load code, enumeration of Active Directory, access to the .NET Framework , traversal using PowerShell remoting, etc…
In short, all an attacker could wish for…


To avoid PowerShell downgrade attacks (from bypassing some of the newer security features), several approaches are available:

  • #1 Remove the PowerShell 2.0 Engine from the OS (including image).
  • #2 Apply application blacklisting (using AppLocker) to deny access to PowerShell 2.0 Engine specific .NET assemblies.

Let’s have a look…

#1 Remove the PowerShell 2.0 Engine from the OS

To avoid a PowerShell downgrade attack, remove the PowerShell version 2.0 and Windows PowerShell 2.0 Engine from the operating system.
This should be done for both the online OS and for the offline OS held in your Windows Image (.wim) file.

Remove PowerShell 2.0 using UI (Windows Features).

Remove PowerShell 2.0 using CLI.

Let’s check the current state of the Windows features, using

Get-WindowsOptionalFeature -Online | Where-Object {$_.FeatureName -match "PowerShellv2"}


Now, let’s remove these Windows features, using

Disable-WindowsOptionalFeature –Online -FeatureName MicrosoftWindowsPowerShellV2Root,MicrosoftWindowsPowerShellV2 –Remove

The –FeatureName parameter specifies the feature to remove. You can specify more than one feature in the same package. Feature names are seperated with a comma.
The –Remove parameter removes the files for an optional feature without removing the feature’s manifest from the image. Using -Remove will reduce the disk space that is used by a Windows image.
After the image has been installed, you can restore the feature at any time from a remote source such as Windows Update or a network share.
The -Online parameter specifies that the action is to be taken on the operating system that is currently running on the local computer.


#2 Apply application blacklisting to deny access to PowerShell 2.0 Engine specific .NET assemblies.

This approach is the most robust solution to block earlier versions of the PowerShell engine by version, even though one should try to do whitelisting instead of blacklisting.
Remember that a PowerShell downgrade attack can also be triggered by other PowerShell hosts or custom built host (“running PowerShell without PowerShell”) by referencing the PowerShell v2 reference assemblies.

Nevertheless, using AppLocker DLL rules, it becomes possible to block specific assemblies.

Steps to take:

  • Enabling AppLocker DLL rule collection.
  • Identifying PowerShell 2.0 Engine specific DLLs to block.
  • Creating AppLocker DLL (path) rules by version.

Enabling AppLocker DLL rule collection.

Using a domain Group Policy or the local Group Policy editor, enable the DLL rule collection in AppLocker.

image image
Enabling DLL rules in AppLocker. Enforcing DLL rules (block versus audit).

Identifying PowerShell 2.0 Engine specific DLLs.

The most robust solution is to block earlier versions of the PowerShell engine by version.
Here’s how to identify those versions per DLL:

powershell -version 2 -noprofile -command "(Get-Item ([PSObject].Assembly.Location)).VersionInfo | fl"
powershell -noprofile -command "(Get-Item ([PSObject].Assembly.Location)).VersionInfo | fl"
powershell -version 2 -noprofile -command "(Get-Item (Get-Process -id $pid -mo | ? { $_.FileName -match 'System.Management.Automation.ni.dll' } | % { $_.FileName })).VersionInfo | fl"
powershell -noprofile -command "(Get-Item (Get-Process -id $pid -mo | ? { $_.FileName -match 'System.Management.Automation.ni.dll' } | % { $_.FileName })).VersionInfo | fl"


For more information, have a look at Lee Holmes blog post here.
All credits go out to his blog post.

Creating AppLocker DLL (path) rules by version.

  • #1 Create the default allow rules (whitelist).
  • #2 Create the custom deny (path) rules (blacklist).

NOTE: Before you enforce DLL rules, avoid shooting yourself in the foot and make sure that there are allow rules for each DLL that is used by any of the allowed applications.
It is recommended to first run in passive/audit mode and validate the dedicated eventlog “AppLocker/EXE and DLL” for missing DLL rules.

For more information: Microsoft TechNet – Enable the DLL Rule Collection, Create AppLocker Default RulesCreate a Path Rule.


PowerShell downgrade attacks can be detected through the classic PowerShell event log (event ID 400) as described here by Lee Holmes, senior member of the PowerShell product group.

Using the PowerShell cmdlet ‘Get-WinEvent’ to detect any downgrades, makes it easy peasy…

Get-WinEvent -LogName "Windows PowerShell" |
Where-Object Id -eq 400 |
Foreach-Object {
$version = [Version] ($_.Message -replace '(?s).*EngineVersion=([\d\.]+)*.*','$1')
     if($version -lt ([Version] "5.0")) { $_ }


Hope this helps…
Kurt Roggen [BE]

Related reading:


2 thoughts on “PowerShell Security: PowerShell Downgrade Attacks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s