PSLockDownPolicy and PowerShell Constrained Language Mode

There have been number of great articles about PowerShell both from an Attack perspective as well as a Defensive perspective, see (Sean Metcalf) as well as (Carlos Perez). The first thing I guess is to mention there are different PowerShell Language modes which you can read about at Much of the 'attack' style PowerShell uses elements that would be blocked via the use of setting PowerShell to Constrained language mode. There are basically a few ways of setting PowerShell to use Constrained Language mode with the recommended method being to utilize either DeviceGuard or AppLocker in enforced mode (with WMF 5+/PowerShell 5+), however the quicker method is to make use of a System Environment variable called __PSLockDownPolicy to configure this.

So the first thing to make note of with regards to __PSLockDownPolicy is that this setting is completely undocumented from a Microsoft perspective. Yes, I'm a Microsoft employee and No this is not official documentation as to how this works from a Product Group but just my observations on how it seems to work from testing. Considering that this is undocumented this typically means unsupported as well so my guess is if you called up / opened a ticket online with CSS odds are you would probably hit a brick wall at some point with regards to doing something not officially supported by us.

When I was first looking at this setting the initial thing I was told was that it was pointless as User Environment variables override System Environment Variables i.e. all the attacking process would need to do was run a set __PSLockDownPolicy= to configure this variable to something else more conducive to that user and start a PowerShell session post setting the User Environment variable to a wanted value and they would be able to do whatever they wanted to anyway with Full Language mode again. Well… once I started testing I quickly found this not to be the case. I've tested trying to use this as a User Environment variable and it doesn't even seem to read it, I've tried the User Environment variable as a possible method of overriding the System Environment variable and that does not work either. I decided to ProcMon it and the procmon logs show that when PoSH starts it always reads in the system environment variable from the reg key HKLM\System\CurrentControlSet\Control\SESSION MANAGER\Environment\__PSLockdownPolicy that defines it and never queries for a User Environment variable with this name. So the end story here is that this absolutely works and that it cannot be overridden easily by an attacking piece of code in user mode space.

So with this thought in mind the next question is how feasible is this in the enterprise. Well since we can't set this into the users environment the first thing you need to recognize is that this is going to be on/off for computers as a whole. There is not going to be an easy method to say this machine is sometimes used by dev x vs sales person y and one has different requirements than others. If you don't have this sort of transient work force per computer account then this 'might' be a usable setting in your environment. One of the other questions I had is if I turn this on how do I know when this is breaking things in my environment? Fortunately this is actually handled fairly well in that whenever a script does not run due to Constrained Language mode we do actually throw a nice event in the PowerShell Operational Event log that specifically says a script was blocked due to Contrained Language mode. These could be collected via WEF/SIEM etc and reported/alerted on both for detection of bad things as well as detection of possible enterprise scripts which are being blocked due to this new setting.

The one last thing with regards to testing this setting that I found interesting was how many security principals that this setting applies to on a system. When using AppLocker w/ Scripts in Enforced mode and PoSH 5+ it's fairly easy to know which users this applies to and also to not apply this to local Administrators on a system as they obviously can turn this off if they desired. It's also quite expected that AppLocker doesn't apply to in the box security principals i.e. System/Network Service etc. So I figured I would test this as well and also observed that when using __PSLockDownPolicy it also affects the built in security principals, i.e. setting task scheduler to run as System and running a script that used Full Language elements was blocked, I also tested this via psexec as well to verify. This is a little more concerning from an enterprise perspective as from what I've seen our internal IT department uses a lot of scripts which run under SCCM etc against my system that would quickly be broken on my system if I were to enforce this.

For grins I also wanted to know what possibly using other settings for this value would reveal ie. whether I could set Language mode to None or anything else. The list below is what I came up with testing with the following and starting new sessions for each iteration.

gc Env:__PSLockdownPolicy

0 = Full Language
1 = Full Language
2 = Full Language
3 = Full Language
4 = Constrained Language Mode
5 = Constrained Language Mode
6 = Constrained Language Mode
7 = Constrained Language Mode
8 = Full Language

So short story the values just seem to either do Full or Constrained I stopped after 8 J if you are bored and want to script some way to test this with more values feel free J.

So my ending thoughts are this is still useful but you need to be aware of usage of undocumented / possibly unsupported settings, be aware of the lack of a method to differentiate based on a user on a system and be aware that this affects system accounts as well on your systems.

Thanks and hopefully this is helpful to someone J Please correct me if you have found differently