UAC bypass via elevated .NET applications

TL;DR .NET Framework can be made to load a profiling DLL or a COM component DLL via user-defined environment variables and CLSID registry entries, even when the process is elevated. This behavior can be exploited to bypass UAC in default settings on Windows 7 to 10 (including the latest RS3 builds) by making an auto-elevate .NET process (such as MMC snap-ins) load an arbitrary DLL.

Introduction

Last May, Casey Smith pointed out on twitter and on his blog that the .NET profiler DLL loading can be abused to make a legit .NET application load a malicious DLL using environment variables.

When reading this, first thing that came to mind was "if this works with elevated .NET processes, this would make a nice UAC bypass as well". And sure enough, it does.

This issue is still unfixed as of this writing – and may remain so – but is already public since July, as it was independently discovered, reported and published on Full Disclosure by Stefan Kanthak.

Bypassing UAC

To make a .NET application load an arbitrary DLL, we can use the following environment variables:

On .NET < 4, the CLSID must be defined via the HKCR\CLSID{GUID}\InprocServer32 registry key containing the path to the profiling DLL. On recent versions, the CLR uses the COR_PROFILER_PATH environment variable to find the DLL – and falls back to using the CLSID if COR_PROFILER_PATH is not defined.

HKCR\CLSID is a combined view of Software\Classes\CLSID in HKLM and HKCU. Creating the CLSID keys in HKLM (or machine-level environment variables) requires elevation, but creating them in HKCU does not. And the catch is, everything works just fine with user-level environment variables and registry entries.

Now we only need an executable that is auto-elevated (no UAC prompt in default settings) and uses the .NET CLR to load our fake profiler DLL. MMC is a good fit for that, in my testing I used the gpedit MMC but there are other usable ones (examples later).

Of course, it also works with UNC paths if you have a reachable SMB share:

COR_PROFILER_PATH=\\server\share\test.dll

Root cause

While the COM runtime does prevent CLSID lookups in user registry (HKCU) when running in elevated processes to prevent such bypasses, the .NET runtime does not – and in this case the lookup is performed by the latter, who appears to shim the component lookup:

The fix would require implementing in CLR checks similar to COM ones.

Additional vectors

Now that we know how CLR behaves, we can find other instances by checking CLSIDs lookups in HKCU with CLR calls in the stack.
One instance also in GPEdit, is the "Microsoft.GroupPolicy.AdmTmplEditor.GPMAdmTmplEditorManager" component (CLSID {B29D466A-857D-35BA-8712-A758861BFEA1} on my test VM):

Looking at the existing entries in HKCR seems to indicate the component itself is implemented in a CLR assembly:

MMC will then load our managed DLL and try to access the TestDotNet.Class1 class.
By default, C# does not have an easy way to create a simple DLL entry point such as DllMain (we don't want to write a module initializer for this because we're lazy), but the class referenced in registry seems to be loaded so we can just use a static constructor to execute our elevated code:

(Note: here the path must be relative, otherwise mmc.exe tries to load C:\Windows\Microsoft.NET\Framework64\v4.0.30319\C:\Temp\test_unmanaged.dll)

Not a security boundary

Microsoft repeatedly stated that UAC is not a security boundary. Security people usually phrase it in a more pragmatic way: do not trust UAC, do not run as split-token admin, always use a non-admin user for your non-admin tasks. I couldn't agree more.

Still, a lot of people run everything as local admin and are interesting targets for pentesters / red teamers – as well as real bad guys. So I guess new techniques are still of interest to some.

For pentest purposes I'd recommend the generic one from @tiraniddo (example implementation here, another one here) as it does not require a DLL load and doesn't seem to be caught by most EDR solutions yet.

Also, if you're into UAC bypasses, there is a lot of resource out there on the topic, but the following are must-reads:

Many thanks to Casey Smith (@subtee) for pointing out the .NET profiler DLL trick, to the helpful MS dev for information on the root cause, and to Matt Graeber (@mattifestation) for his advices and his review of this post.

Timeline

2017-05-19 Bypass found

2017-05-20 Email sent to MSRC (cc'ing an MS dev as suggested by @mattifestation)

2017-05-22 MSRC case #38811 open

2017-05-20/23 Discussion with MS dev, additional info

2017-06-24 Reply from MSRC: "We have finished our investigation and determined this does not meet the bar for servicing downlevel. UAC is not a security boundary."

2017-07-05 Publication of the profiler bypass on Full Disclosure by Stefan Kanthak

2017-09-15 Publication of this post

Published on
Fri 15 September 2017
Edited on
Wed 11 April 2018
By
@clavoillotte