It was a routine patch Tuesday and I was developing signatures for Qualys VM to identify vulnerabilities. But when I glanced at CVE-2015-1635 it was clear immediately that there was nothing routine about it. It’s a critical vulnerability which can allow remote attackers to take complete control of IIS web servers without have any prior credentials to the server. Now that is BIG! After releasing the VM signature I started working on this blog post which explains a bit further on how an attack using CVE-2015-1635 works. The blog post also explains the working of Qualys VM QID 91041.
HTTP Range allows HTTP clients to fetch the specified offset within the file on the HTTP server. It’s mainly used for ‘Resume broken downloads’. Let’s say you are downloading a file called ‘welcome.png’ whose file size is 2KB. And after downloaded the first 1 KB your client browser crashed. The next time, your browser or your downloading tool just needs to send the Range request 'Range : 1024-2048' to get the rest of the file. In this article I will refer to the lower boundary (1024 in the example) as 'range-low' and higher boundary (2048) as 'range-high'.
Windows Kernel Caching is implemented in HTTP.sys. It provides caching for HTTP requests in Windows kernel to increase performance for IIS. The real technical details were never exposed to the public, but with some poking around we can find that out.
Let’s say an HTTP client requests the file 'welcome.png' by using
curl -H 'Range: bytes=1-1000' http://YOURHOST/welcome.png
Then, the 'UlpParseRange()' function will extract the range from the request. Since it’s the first time the request is received, the Windows kernel will find that the cache is missing. And therefore 'CacheMiss' function is called.
The ‘UlAdjustRangeToContentSize’ function is a security check function which checks if the range in our request exceeds the real content size. So if we send the range-high as 1,000,000 the return value of this function should return us the content size. Hex 0x3e8 is 1000 in decimal and it’s the length of the range we requested because we didn’t exceed the content size here.
You can see that the 'BuildCacheEntry' wasn’t called, so no cache is building at this time. So Windows Kernel Caching won’t generate the cache at the first time of request. If the same request is sent the second time, here is what we see:
After 'prepared' in the first request, the Windows kernel will build a Cache Entry in Breakpoint 3 (figure above), allocate the buffer size given by 'UlAdjustRangeToContentSize' function, then send the cache back by function 'UlpSendCacheEntry'.
So Windows will only send us the cached content after building the Cache Entry. This is why hackers need at least 2 identical requests to exploit CVE-2015-1635.
The vulnerability is an integer overflow in 'UlAdjustRangeToContentSize'. Let’s say IIS receives a request with HTTP header 'Range: bytes= range-low – range-high ' to get some file. The 'security check' function will check if range-low + (range-high – range-low + 1) is less than the real file size. In short it will check if range-high + 1 is less than the real file size.
The range-low and range-high are defined as 'long long' which is a 64 bit value in memory. The maximum value is 0xFFFFFFFFFFFFFFFF. So when the range-high is 0xFFFFFFFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF + 1 will be 0 – which is less than the file’s size.:-)
Below is a debugger screenshot of HTTP request with 'Range: bytes=6000-18446744073709551615' to get ‘welcome.png’.
After an attacker sends the malformed HTTP Range request. UlAdjustRangeToContentSize 'thinks' the request length is ok and it will return the length. Later, 'BuildCacheEntry' will create that size of kernel cache and 'SendCache' function will send all data out until it reaches some address which can’t be read. This causes the blue screen of death and the machine crashes. So exploiting this vulnerability is really half 'SSL Heartbleed' and half 'DOS'.
The same integer overflow also happens in 'UlpParseRange' and 'UlpDuplicateChunkRange', but I think that 'UlAdjustRangeToContentSize' is most critical.
Here is a bindiff for Microsoft Patch MS15-043.
All the 3 functions that have this integer overflow issue are fixed. The fix is simply calling a function 'RtlULongLongAdd'.
When the range-low + (range-high – range-low + 1) overflows ‘RtlULongLongAdd’ will return an error ' STATUS_INTEGER_OVERFLOW'. 'UlAdjustRangeToContentSize' function will return length of the real file instead of the range.
Qualys Remote Detection - QID 91041
Qualys VM has released an unauthenticated QID 91041 to detect this vulnerability. The QID probe will hit the vulnerable function 'UlpParseRange' and ''UlAdjustRangeToContentSize' to tell if a remote machine is patched or not.
The check sends a HTTP request with 'Range: bytes=0-18446744073709551615'. And you can also send a similar probe by running the following Linux command:
curl -H 'Range: bytes=0-18446744073709551615' http://YOURHOST//
Patched Target Behavior:
On patched targets the function 'UlpParseRange' function returns the error code 'STATUS_INTEGER_OVERFLOW'. This error code will cause Windows Kernel Caching to return the response without calling 'CacheMiss' function and 'UlAdjustRangeToContentSize'.
Vulnerable Target Behavior:
The request will get pass the function 'UlpParseRange' without any error (Eax return is 0). Then 'CacheMiss' function will be called, so is 'UlAdjustRangeToContentSize'. In 'UlAdjustRangeToContentSize' when the Length of our range is 0XFFFFFFFF. The return value of this function is eax = 0,edx = 0. The IDA screen capture is below:
Live time debugger’s screen capture for vulnerable target is below:
As you can see, when we send the request, 'UlAdjustRangeToContentSize' will return us a 0, 0 on Eax and Edx. So the function 'UlPrepare416Response' will be called. And that is why vulnerable targets will return a 416 HTTP code. Patched targets don’t have a chance to get to this point in the execution.
So in short, IIS servers which return HTTP 416 for our request are vulnerable.
The remote detection is very reliable and safe because:
- Even if the attacker sends the request before we do – Windows kernel won’t build the cache entry because of the 416 jump, so we can hit the vulnerable code here every time rather than reading from the kernel cache.
- Because of no cache created. We won’t hit the 'BuildCache' and 'SendCache' to cause a DOS on the target we are scanning.
It is reported that the vulnerability is actively exploited in the wild and the DOS exploit code is also widespread. Therefore I suggest that you apply the patch for this issue as soon as possible.