When looking for User Mode Code Integrity (UMCI) bypasses in the context of Device Guard, my typical approach has been to hunt for Microsoft-signed PowerShell scripts/modules that contain some sort of functionality that allows for unsigned code execution. Fortunately, there are plenty that exist on Windows 10 by default. One such bypass was CVE-2017-0215, which abused the “PSWorkFlowUtility” PowerShell module to execute arbitrary unsigned PowerShell code.
You might ask, how? Looking at this module, you will notice that it has an embedded Authenticode signature block, which makes it portable (an attacker can bring the old, vulnerable version to their target as the certificate and file hash is included in the file). What else stands out to you?
We know that in the context of UMCI, the most locked down scenario is only allowing Microsoft signed code to run. Since this module is signed by Microsoft, it will be permitted to execute in Full Language Mode (as opposed to Constrained Language Mode). This particular module simply takes PowerShell code via a parameter (as a string) and continues to call “Invoke-Expression” on it, which simply executes it.
As you can see, taking the square root of Pi is blocked in Constrained Language Mode as method invocation on most .NET types are not permitted. If we import the PSWorkFlowUtility module and pass that code via the “-Expression” parameter, it will execute in Full Language Mode, giving us the result. To mitigate this, Microsoft fixed the built-in module by removing the authenticode signature and adding “[System.Security.SecurityCritical()]”, which makes the module security transparent and subject to Constrained Language Mode (CVE-2017-0215).
Since the old, vulnerable module file was Authenticode signed (making it portable), it is required to block the old, vulnerable version of this module. This can be done via the file’s hash, but it is essential that all previous, vulnerable versions of the file be accounted for – a responsibility that should honestly lie on Microsoft since they are able to collect the hashes across all Windows builds where the vulnerable module is present.
Another viable mitigation for blocking the old, vulnerable version is to note that the embedded signature is not Windows-signed (i.e. has the Windows System Component Verification EKU – 126.96.36.199.4.1.3188.8.131.52). Device Guard CI policies permit limiting a publisher rule by a specific enhanced key usage (EKU). A stricter policy would allow only Windows-signed code to run – a rule that’s present in Matt Graeber’s Device Guard base reference policy. If only Windows-signed code is permitted to execute, the vulnerable version of Invoke-AsWorkflow would be limited to execution within constrained language mode, effectively mitigating the previous vulnerability. Now, vulnerable code can and will be Windows-signed but in this case, this vulnerability is effectively mitigated with a stronger Device Guard CI policy.
The old, vulnerable version of the module can be found here: https://gist.github.com/enigma0x3/dca6f24b2b94e120daae37505d209ecc