by Mitja Kolsek, the 0patch Team
This is a story of a publicly known remote code execution vulnerability that somehow got ignored and mostly overlooked for four and a half years, meanwhile rediscovered a number of times, weaponized, and finally fixed this October with an unexpected acknowledgment.
Back in May 2017, security researcher Shay Ber published details about a vulnerability affecting Windows DNS Service. They found that any member of the DNSAdmins domain group can use their privileges to get the computer running the DNS Service to run arbitrary code as Local System - which equals a complete takeover of said computer. Since many domain controllers are running the DNS Service, this could mean a complete Windows domain takeover.
Note that DNSAdmins is a powerful group: being able to change domain DNS configuration allows one to cause all sorts of problems - but it doesn't automatically allow them to take over the domain. Typically, a DNS administrator is not allowed to login to the computer running the DNS Service (often a domain controller), and configures DNS from their own workstation using a DNS management tool that utilizes remote procedure calls (RPC).
Shay noticed that legitimate functionality of these remote procedure calls, conveniently made available by the Windows dnscmd.exe tool, can be used to remotely configure a DNS Service to load an arbitrary DLL from a network share and execute its code. This is due to the DNS Service checking, upon startup, the presence of registry value ServerLevelPluginDll and, if it exists, loading the DLL from the path specified therein. This value could be set remotely using the dnscmd.exe tool by members of the DNSAdmins group.
After reporting this to Microsoft, Shay was told that "it [would] be fixed by basically only allowing DC administrators to change the ServerLevelPluginDll registry key, and that it [would] be possible to toggle this feature off in future releases."
Although in their write-up Shay generously described the issue as "a cute feature (certainly not a bug!)", it was undeniably a privilege escalation - from non domain admin to domain admin - as well as a remote code execution issue - from computer one can run code on to computer one can't normally run code on.
In any case, the issue got largely forgotten, likely because of its "not a bug" classification, until another security researcher Sean Metcalf brought it up in their article a year and a half later. Fourteen months after that, security researcher Dhiraj Sharma revisited the original article and published a new write-up. Further nine months of nothing happening later, an exploit module got added to Metasploit, making it easier for attackers of all hat colors to exploit this issue. We're not done yet. In April and May 2021, security researcher Phakt wrote a two-part analysis of this issue, further dissecting it and shedding light on the "why" and offering an alternative for using the overly-permissive DNSAdmins group.
Finally, this October's Windows Updates brought a fix, assigned the issue CVE-2021-40469, and, surprisingly, acknowledged (only) Yuki Chen, a regular reporter of Windows vulnerabilities, for finding and reporting this to Microsoft.
What a ride.
Microsoft's fix delivered by October 2021 updates did what was promised back in 2017: it introduced a new registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\DNS\InternalParameters with permissions only allowing administrators to modify its content. The ServerLevelPluginDll value has been moved from the Parameters key to this new key, making it impossible to change it with permissions provided by the DNSAdmins group. A decent fix, as far as we can tell.
The only thing not fixed were affected Windows servers that aren't receiving official Windows updates anymore: Windows Server 2008 R2 and Windows Server 2008 without Extended Security Updates. This is where we come in.
Our micropatch, the whole 19 CPU instructions of it, takes a bit different approach compared to Microsoft's fix. When applied (enabled), our micropatch forces the DNS Service to ignore the ServerLevelPluginDll value even if it's present in the registry. Based on the absolute absence of hits when googling for ServerLevelPluginDll and filtering out articles about exploiting this value, we assess that very few organizations are using this feature, so simply removing it - which our patch effectively does - is the optimal approach. This decision was in line with our goal of using minimal amount of code to fix the vulnerability. However, should a customer report that they're using this feature, we'll replace the patch with one that is functionally closer to Microsoft's.
Source code of our micropatch:
push rdi ; storing register on stack to keep their values
call VAR ; trick to get address of string on stack
dw __utf16__('ServerLevelPluginDll'), 0
pop rdx ; rdx points to string 'ServerLevelPluginDll'
mov rcx, rdx ; rcx points to string 'ServerLevelPluginDll'
mov rdx, r12 ; rdx points to name of registry value that
; is currently being read from registry
sub rsp, 20h ; create home space for the 64bit call
call PIT__wcsicmp ; compare strings case insensitive
add rsp, 20h ; abandon home space
pop r9 ; restoring register values from stack
cmp rax, 0 ; were strings equal?
je PIT_0x41171 ; if so, bypass reading from registry
And the video of our patch in action.
This micropatch was written for:
- Windows Server 2008 (updated with January 2020 Updates - latest before end of support)
- Windows Server 2008 R2 (updated with January 2020 Updates - latest before end of support)