Microsoft PowerShell is an immensely powerful administrative framework, providing deep, programmatic access to the Windows operating system and the underlying .NET architecture. Unfortunately, this same power makes PowerShell a primary vector for malware, ransomware, and advanced persistent threat (APT) actors. Traditional defences, such as altering the PowerShell Execution Policy, offer virtually no real security, as attackers can easily bypass them using simple command-line flags. To genuinely mitigate malicious script execution, enterprise administrators must implement PowerShell Constrained Language Mode.
The Fallacy of the Execution Policy
A common misconception among system administrators is that setting the PowerShell Execution Policy to Restricted prevents script execution. This is incorrect. The Execution Policy is designed to prevent users from accidentally running scripts, not to stop malicious actors. An attacker can trivially bypass this control by launching PowerShell with the -ExecutionPolicy Bypass parameter, or by piping the script contents directly into standard input without saving a file.
Understanding Constrained Language Mode
Constrained Language Mode (CLM) is a security feature designed to cripple the capabilities of PowerShell when running untrusted code. When CLM is enforced, PowerShell functions normally for basic administrative tasks and standard cmdlets, but it severely restricts access to the advanced programming features typically abused by malware.
Specifically, when a PowerShell session operates in Constrained Language Mode, the following restrictions apply:
- Direct access to the Windows API via P/Invoke is blocked.
- The ability to instantiate arbitrary COM objects is prevented.
- The use of the
Add-Typecmdlet to compile and run inline C# code is blocked. - Access to advanced .NET types is severely restricted; only a small whitelist of approved, safe .NET classes is permitted.
By enforcing these restrictions, CLM effectively neutralises complex PowerShell attack frameworks, such as Empire or PowerSploit, which rely heavily on direct API interaction and inline compilation.
Enforcing CLM via AppLocker
While you can manually set Constrained Language Mode for a single session using the $ExecutionContext.SessionState.LanguageMode variable, this is useless for enterprise security, as an attacker can simply change it back. The robust, supported method for enforcing CLM across a fleet of Windows devices is through integration with Windows AppLocker or Windows Defender Application Control (WDAC).
When an AppLocker policy is applied to a system (specifically, when script rules are enforced), PowerShell automatically detects the presence of the application whitelisting policy. By design, PowerShell will instantly drop into Constrained Language Mode for any script that is not explicitly permitted by the AppLocker rules.
To implement this via Group Policy:
- Open the Group Policy Management Editor and navigate to Computer Configuration > Policies > Windows Settings > Security Settings > Application Control Policies > AppLocker.
- Click on Script Rules.
- Right-click and select Create Default Rules. This ensures that scripts located in highly secure, administrator-controlled directories (like
C:\WindowsandC:\Program Files) are permitted to run. - Right-click the main AppLocker node, select Properties, check the box next to Configured under the Script rules section, and set the enforcement mode to Enforce rules.
The Impact of AppLocker Integration
Once this policy applies to the endpoint, the behaviour of PowerShell fundamentally changes. If a user (or an attacker) attempts to run a script located in a non-whitelisted directory (such as their Downloads folder or the Temp directory), AppLocker will technically block the script file itself. However, if the attacker attempts to run malicious commands interactively at the prompt, or pipes a payload directly from memory, PowerShell will execute the commands—but strictly within Constrained Language Mode.
Because the attacker’s context is not trusted by AppLocker, they cannot utilise the advanced .NET integration required to inject shellcode, interact with the LSASS process, or establish complex command-and-control beacons. The system remains secure, while legitimate administrative scripts stored in secure directories continue to run with full language capabilities.