By Mitja Kolsek, 0patch Team
Two days ago, an interesting exploit by Zhiniang Peng and Chen Wu was published on GitHub. It's a simple Python script that you launch against any 32-bit Windows Server 2003 with WebDAV functionality enabled, and it executes calc.exe on that server. Needless to say, this exploit could easily be modified to download a malicious executable to the server and launch it, and we confirmed it can also be used against 64-bit servers. To fully compromise a server, the exploit would have to escalate its privileges from NETWORK SERVICE user to LOCAL SYSTEM, where tricks like this one or this one might come handy.
Now, what makes this exploit so interesting?
- According to Shodan, there are a little over 600,000 publicly accessible IIS 6.0 servers on the Internet, most of them likely running on Windows Server 2003. (This doesn't include an unknown number of servers not accessible from the Internet.)
- The extended support period for Windows Server 2003 ended 20 months ago, so there is no official security fix for this issue.
Fortunately, most 2003 servers don't have WebDAV functionality enabled, but it is likely enabled on many SharePoint Portal servers and sites that use it for remote Web authoring or similar. Iraklis Mathiopoulos ran a sample probe to determine how many of Internet-connected 2003 servers are WebDAV-enabled and found that approximately 10% of them are. So we seem to have about 60,000 servers out there exposed to this freshly published exploit.
Sure, some will say that everyone should have stopped using Windows Server 2003 long ago as it doesn't get security patches any more. But owners of these servers each have their own story, their own set of constraints to work within, and their own budgets that they would rather spend on something other than upgrading a server that works.
To help maintainers of Windows Server 2003 computers block almost inevitable attacks under these unfavorable circumstances, we decided to provide them a free solution: a micropatch for CVE-2017-7269, which they can apply on their machines not only without rebooting, but also without even restarting Internet Information Services. (For those new to 0patch, this is how we believe applying security patches should always look like.)
Let me describe the process of analyzing this vulnerability and creating a micropatch.
The first step in our analyses is always reproducing the exploit or proof-of-concept. In this case, it was trivial: exploit.py provided by Peng and Wu was simple and effective, and it reliably launched calc.exe on a local 32-bit Windows Server 2003.
A full-blown exploit is usually not ideal for analyzing the vulnerability; especially in a buffer overflow case, we would prefer the process crashing at its vulnerable location to launching the calculator. So I "dumbed down" the exploit by replacing all of its shell code with A's (0x41 bytes). I also used WinDbg's Global Flags to enable Page Heap on the vulnerable w3wp.exe process in hope to catch the buffer overflow immediately as it occurs.
Sure enough, after attaching to w3wp.exe and launching my simplified poc.py, an access violation was caught due to writing beyond an allocated memory block. The offending instruction was at location httpext!ScStoragePathFromUrl+0x360:
rep movs dword ptr es:[edi],dword ptr [esi]
Those familiar with Intel machine code will recognize rep movs as a memory copying procedure. So apparently the overflow occurs when one buffer is copied into another - which is not large enough. Setting a breakpoint at the beginning of the memory-copying code block revealed that a buffer pointed to by esi (source) contained a 433-wide-character string (i.e., 868 bytes including the trailing zero) "/aaaaaaaAAA...(many A's)...AAA>", which is the exact value of one of the tokens sent in the WebDAV request by poc.py. The destination buffer pointed to by edi, however, was approximately 50% too small to receive the string. This provided an initial hint that it could be a case of using wide (two-byte) characters but allocating a buffer based on the number of characters instead of the number of bytes they take.
Anyway, in order to find where the too-small destination buffer pointed to by edi was allocated, I used this nifty WinDbg extension !heap -p -a edi, which gave:
It seemed like the allocation of our destination buffer occurred in httpext!HrCheckIfHeader as a result of calling CStackBuffer::resize.
lea ecx, [ebp-430h]
test eax, eax
So I set a breakpoint at the top of this code block to see what this resize was all about. It turned out that the local variable [ebp-434h] holds the length (in characters) of the input string, and the above code wants to resize an existing buffer to be able to hold this entire string.
My suspicion was confirmed: instead of specifying the number of bytes to allocate, the above resize call gets the number of characters - making the resulting buffer much too small. Looking around the above code, I found a similar code block where this calculation is done correctly, which further confirmed my analysis.
At this point, it was easy to decide how to patch: instead of pushing [ebp-434h] to stack as an argument to the resize function, we should push that same value multiplied by two and further increased by 2, to accommodate for the string-teminating zero. Since I didn't want to modify the local variable [ebp-434h] as it is also being used elsewhere, I decided to replace the push [ebp-434h] instruction with the following:
mov eax, dword [ebp-434h]
lea eax, [eax+eax+2]
So I wrote a .0pp file for this and built a 0patch using our 0patch Builder (which, by the way, anyone can download as part of 0patch Agent for Developers), then I relaunched poc.py to see if my micropatch fixed the bug.
To my surprise, w3wp.exe still had an access violation in the same place. After checking that I didn't snafoo the patch, I traced the flaw again in the same way as before, only to discover that another, almost identical, incorrect buffer allocation takes place elsewhere in the code. Specifically, in httpext!CParseLockTokenHeader::HrGetLockIdForPath.
This second flaw was logically identical to the first one, and could be patched with effectively the same patch code using an additional patchlet. It was time to give poc.py another try and see how IIS reacts to it with the two patchlets applied.
Triumphantly, IIS returned "HTTP/1.1 207 Multi-Status" instead of reporting internal server error, and it did the same when the original exploit.py was launched against it. This was it, the bug was fully patched.
In conclusion, let's see the source code of this micropatch.
You can see that the patch comprises two patchlets, both containing the same three machine code instructions, just at different offsets into the vulnerable httpext.dll and with different offsets to the relevant stack-based local variable. Extremely lightweight, extremely unlikely to cause unwanted side effects, and extremely reviewable - just the way micropatching is supposed to be.
If you have 0patch Agent installed on your Windows Server 2003, patches ZP-269 and ZP-270, for 32-bit and 64-bit server, respectively, should already be present and applied. If not, you can download a free copy of 0patch Agent to protect your server from CVE-2017-7269. Note, however, that these patches are only applicable to a fully updated server with Service Pack 2 installed, as they were made specifically for 32-bit and 64-bit httpext.dll version 6.0.3790.4518. (Check your httpext.dll version in C:\Windows\System32\inetsrv.)
Finally, you should know that we're still in beta so keep an eye on your server and report any unusual events to us at firstname.lastname@example.org. Also let us know if you have some other version of httpext.dll and we'll try to port the patch for you.