by Mitja Kolsek, the 0patch Team
Update 1/19/2022: User informed us that our initial micropatch for this issue broke Windows Hello PIN settings (not the login, but creating or editing the PIN). We analyzed the issue and found that our initial test for requestor's permissions was causing the problem in this case. We therefore revoked the initial patches and issued new ones where we check for requestor's permissions using token's TokenIsElevated value.
Update 3/17/2022: March 2022 Windows Updates modified rpcss.dll on some Windows versions, so we ported our micropatch to these new versions. This vulnerability was presented to Microsoft employees at the Microsoft BlueHat Israel conference.
Update 6/27/2022: We've ported our patches for this issue to current versions of supported Windows platforms. Given that Microsoft does not plan to patch authenticated credentials relaying issues, these patches are now available only to PRO and Enterprise accounts.
Update 10/21/2022: Microsoft silently fixed this issue with October 2022 Updates. No CVE ID was assigned.
Back in April 2021, researcher Antonio Cocomazzi of Sentinel LABS and independent security researcher Andrea Pierini published an article titled Relaying Potatoes: Another Unexpected Privilege Escalation Vulnerability in Windows RPC Protocol. The article described a local privilege escalation vulnerability they had found in Windows and reported to Microsoft, who decided not to fix because "Servers must defend themselves against NTLM relay attacks."
As far as real world goes, many servers do not, in fact, defend themselves against NTLM relay attacks. Since the vulnerability is present on all supported Windows versions as of today (as well as all unsupported versions which we had security-adopted), we decided to fix it ourselves.
The Vulnerability
The vulnerability is comprehensively described in the Sentinel LABS article. It allows a logged-in low-privileged attacker to launch one of several special-purpose applications in the session of any other user who is also currently logged in to the same computer, and make that application send said user's NTLM hash to an IP address chosen by the attacker. Intercepting an NTLM hash from a domain administrator, the attacker can craft their own request for the domain controller pretending to be that administrator and perform some administrative action such as adding themselves to the Domain Administrators group.
To exploit this issue, some higher-privileged user must be logged in to the same Windows computer as the attacker at the same time. This is a relatively exotic situation on Windows workstations, although possible with the "Switch user" feature that keeps one interactive user logged in while another user logs in interactively to the same computer. One could imagine a malicious user asking the administrator to help them resolve some issue by logging in to their workstation via "Switch user", where a script running in attacker's session would autonomously perform the attack.
A much more interesting target are Windows servers, especially terminal servers where multiple users, both unprivileged and administrators, have simultaneous user sessions. There, any remotely logged-in user could attack any other user that is currently also logged in, and do so without social engineering or any assistance by the victim.
Microsoft decided not to fix this issue as their position is that NTLM relaying should be prevented through NTLM configuration or by not using NTLM anymore. Such changes have potential to break something, so companies are understandably a bit reluctant to implement them in their production environments.
Patching This Issue
To fix a vulnerability, one must first understand it, and the functionality within which it resides. This immediately brings us to the question: Why does Windows allow a low-privileged user to cause anything to get
launched at all in a session other than their own? Unfortunately, we failed to find
the answer or even come up with some hypothetical reason. A feature like this would immediately look suspect to any security researcher, and
it's not a surprise that someone had looked at it and found it to be
exploitable.
It would be easy for us to completely disable this strange functionality but just in case it's being used for some reason, we decided to only block it in case a non-privileged user is trying to launch a process in another session. We wanted to allow privileged users such as administrators or Local System to use this mysterious feature if they wanted, but prevent normal users from doing so.
Our patch was therefore placed in the execution flow of rpcss.dll where requestor's token, requestor's session ID and target session ID are known, and does the following:
- If requestor's session ID is the same as target session ID, we do not interfere. (If the user wants to launch processes in their own session, let them do it.)
- Else, if requestor's token allows for some privileged operation, we do not interfere. (Specifically, if requestor's token has a non-zero TokenIsElevated value, we consider the requestor as privileged, and do not interfere.)
- Else, we block the operation.
Source code of our micropatch:
MODULE_PATH "..\Affected_Modules\rpcss.dll_10.0.19041.1387_Win10-21H1_64-bit_u202112\rpcss.dll"
PATCH_ID 749
PATCH_FORMAT_VER 2
VULN_ID 7190
PLATFORM win64
patchlet_start
PATCHLET_ID 1
PATCHLET_TYPE 2
PATCHLET_OFFSET 0x115b8
N_ORIGINALBYTES 5
JUMPOVERBYTES 0
PIT rpcss.dll!0xec38,Advapi32.dll!ImpersonateLoggedOnUser,Advapi32.dll!RevertToSelf,Advapi32.dll!RegOpenKeyExW,Advapi32.dll!RegCloseKey,rpcss.dll!0x11560
code_start
push r12 ; store registry values
push r13
push r9
sub rsp, 8 ; align stack
mov r13, [rbp-68h] ; this - contains current session token
mov rcx, [r13+48h] ; get current session token
sub rsp, 20h ; home space
call PIT_0xec38 ; call GetSessionId2, returns session id
add rsp, 20h ; restore stack pointer
mov r12, rax ; store session id
mov rcx, [rbp-58h] ; target session token
sub rsp, 20h ; home space
call PIT_0xec38 ; call GetSessionId2, returns session id
add rsp, 20h ; restore stack pointer
cmp rax, r12 ; compare both session ids
; equal -> continue normal code flow
; not equal -> check if current session has elevated privileges
je CONTINUE
mov rcx, [r13+48h] ; current session token
sub rsp, 20h ; home space
call PIT_ImpersonateLoggedOnUser ; the user is represented by token handle
; If the function succeeds, the return value is nonzero
add rsp, 20h ; restore stack pointer
cmp eax, 0 ; check return value
je CONTINUE
mov r12, 0 ; r12 stores a value that decides if requestor has
; elevated privileges
; 0 - not elevated
; 1 - elevated
mov rcx, 0FFFFFFFF80000002h ; handle to HKLM registry, hKey
call VAR
dw __utf16__('SOFTWARE'), 0
VAR:
pop rdx ; rdx points to string "SOFTWARE", lpSubKey
mov r8, 0 ; ulOptions - must be 0
mov r9, 4 ; samDesired - write access desired
sub rsp, 30h ; home space + 10h to store vars on stack
mov qword[rsp+28h], 0 ; resulting handle is at rsp+28h
lea rax, [rsp+28h] ; get address of handle
mov [rsp+20h], rax ; phkResult
call PIT_RegOpenKeyExW ; Opens the specified registry key
mov r13, [rsp+28h] ; store return value
add rsp, 30h ; restore stack pointer
cmp eax, 0 ; If the call succeeded, return value is ERROR_SUCCESS(0).
jne REVERT ; jmp if function fails
mov rcx, r13 ; hKey
sub rsp, 20h ; home space
call PIT_RegCloseKey ; Close the handle of registry key
add rsp, 20h ; restore stack pointer
mov r12, 1 ; elevated privileges
REVERT:
sub rsp, 20h ; home space
call PIT_RevertToSelf ; RevertToSelf terminates previous impersonation
add rsp, 20h ; restore stack pointer
cmp r12, 1 ; check if current user has elevated privileges
je CONTINUE ; jmp if yes
add rsp, 8 ; restore stack to original state before patch
pop r9
pop r13
pop r12
jmp PIT_0x11560 ; skip to end of the function, blocking the attack
CONTINUE:
add rsp, 8 ; restore stack to original state before patch
pop r9
pop r13
pop r12
code_end
patchlet_end
Here is a video showing how the micropatch blocks exploitation of this vulnerability:
Micropatch Availability
This micropatch was written for:
- Windows 10 v21H1 32&64 bit updated with December 2021 or January 2022 Updates
- Windows 10 v20H2 32&64 bit updated with December 2021 or January 2022 Updates
- Windows 10 v2004 32&64 bit updated with December 2021 or January 2022 Updates
- Windows 10 v1909 32&64 bit updated with December 2021 or January 2022 Updates
- Windows 10 v1903 32&64 bit updated with December 2021 or January 2022 Updates
- Windows 10 v1809 32&64 bit updated with May 2021 Updates
- Windows 10 v1803 32&64 bit updated with May 2021 Updates
- Windows 7 32&64 bit updated with January 2020 Updates (no ESU)
- Windows 7 32&64 bit updated with January 2021 Updates (year 1 of ESU)
- Windows 7 32&64 bit updated with December 2021 or January 2022 Updates (year 2 of ESU)
- Windows Server 2019 64 bit updated with December 2021 or January 2022 Updates
- Windows Server 2016 64 bit updated with December 2021 or January 2022 Updates
- Windows Server 2012 R2 64 bit updated with December 2021 or January 2022 Updates
- Windows Server 2012 64 bit updated with December 2021 or January 2022 Updates
- Windows Server 2008 R2 64 bit updated with January 2020 Updates (no ESU)
- Windows Server 2008 R2 64 bit updated with January 2021 Updates (year 1 of ESU)
- Windows Server 2008 R2 64 bit updated with January 2022 Updates (year 2 of ESU)
- Windows Server 2008 64 bit updated with January 2020 Updates
0patch and Microsoft Extended Security Updates