Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=779

Windows: Custom Font Disable Policy Bypass
Platform: Windows 10 Only
Class: Security Feature Bypass

Summary:
It’s possible to bypass the ProcessFontDisablePolicy check in win32k to load a custom font from an arbitrary file on disk even in a sandbox. This might be used as part of a chain to elevate privileges. If anything this is really a useful demonstration that you probably really want to shutdown the object manager directory shadowing as part of the sandbox mitigations, even if you don’t fix the explicit bypass.

Description:

The Process Mitigation policy ProcessFontDisablePolicy disables loading fonts from memory or by a path other than in the system fonts directory. This is probably mostly redundant with the introduction of the User Mode Font Driver, although there’s some interesting additional attack surface if you could compromise that process (it is running with a locked down DACL to prevent people attacking it, presumably). Also while UMFD runs in an AppContainer it might be less restrictive than other sandboxes providing a limited sandbox escape (again to just open up additional attack surface).

The issue is due to a race condition in the check which looks similar to the following:

int WIN32K::bLoadFont(...) {
  int load_option = GetCurrentProcessFontLoadingOption();
  bool system_font = true;
  if (load_option) {
    HANDLE hFile = hGetHandleFromFilePath(FontPath); <- First open of path
    BOOL system_font = bIsFileInSystemFontsDir(hFile); <- Should return True
    ZwClose(hFile);
    if (!system_font) {
      LogFontLoadAttempt(FontPath);
      if (load_option == 2)
        return 0;
    }
  }
  // Switch out path here
  HANDLE hFont = hGetHandleFromFilePath(FontPath); <- Will open our custom font
  // Map font as section
}

There’s a clear race between opening the font and checking its location and then re-opening it again to map the file as a section for processing. If you could make the first check open a file in the system font directory then it’d pass the check. If you then switch out the font for your custom one it’ll load that instead. Previously I’d do this using symbolic links, such as mount points or object manager links but that’s pretty much no longer available in sandboxes anymore. So instead I’ve abused object manager directory shadows again. You can construct a native NT path in such a way that it will first open a system font file, then using a oplock to win the race we can switch the directory object to point to our custom font on disk.

Note: I effectively presented this at the Troopers conference and even said how I did it so this is sort of been publicly disclosed. But that was using object manager symbolic links, and due to the way the font files are loaded this wasn’t usable in a sandbox due to it opening the files at kernel privilege. I pointed out to the attendees that I didn’t think it was easy to exploit in a sandbox so it wasn’t a problem. I’ve spoke to Gavin Thomas about this, he wanted the PoC sending even if unexploitable. As this seems to be more of a problem thought I’d send into secure@.

Proof of Concept:

I’ve provided a PoC which will demonstrate the bypass. It should be executed at low integrity using psexec or modifying the executable file’s ACL to low.

1) Extract the PoC to a location on a local hard disk which is writable by a low IL user. This is necessary as the PoC needs to copy a font file to the applications directory. You also need a copy the pacifioc.ttf font file into the same directory.
2) Execute the poc executable file as low integrity.

Expected Result:
It shouldn’t be possible to load a custom font from disk if it’s outside of the system font location.

Observed Result:
The font is loaded and can be used with GDI.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/39993.zip