Exploiting IIS via HTMLEncode (MS08-006)

    [晴 February 15, 2008 11:39 | by ]
The second Tuesday of every month is a busy time at BreakingPoint; myself and the rest of the security team stop what we are doing and focus entirely on the patches released by Microsoft. Using IDA Pro in conjunction with BinDiff, we compare the existing files with their patched versions and review the differences. This month, Microsoft released patches for seventeen individual vulnerabilities split into eleven different bulletins. Two of these were denial of service flaws, one was a local privilege escalation issue, seven were related to Microsoft Office, four were part of a cumulative update to Internet Explorer, two were client-side (OLE and WebDAV), and only one was what most folks consider a real, “remote” vulnerability. In this article, I will walk through the process of finding, investigating, and exploiting MS08-006.

The first step to finding the flaw is to download the patch. Microsoft makes the patch files available hours before the actual security bulletins are posted, giving us a head start. The easiest way to download the patches is by browsing to Microsoft.com, searching for all results in the Downloads category, and sorting the results by date. The latest security patches will be listed first. In this case, I was using a target system running Windows XP Pro, so I downloaded the WindowsXP-KB942830-x86-ENU.exe patch installer. Instead of just double-clicking the file, I opened up a command shell and ran the installer with the /x:output parameter. This causes the installer to extract the patched files into a directory called output. Inside the output directory are three subdirectories, two of which are SP2GDR and SP2QFE. Since my target machine is running the release version of XP, I opened the SP2GDR directory in Windows Explorer. In most cases, the "GDR" directories refer to the retail or OEM version, while the "QFE" directories refer to the checked/debug version. Inside this directory is a single file, named asp51.dll. This is the only file changed by this particular patch. This file is the core of Active Server Page (ASP) support in Microsoft's IIS web server. ASP support is enabled by default in IIS 5.1 on Windows XP.

Now that we have the patched file, we need to find the unpatched version, and analyze both with IDA Pro. A quick search of the XP system showed no file named “asp51.dll”, however, there was a DLL named “asp.dll”. To verify that these are the same components, I checked the “Internal Name” field of the version information in each file. Both were set to “asp.dll”. I copied both of these files into a new directory and renamed the new version to “patched_asp.dll”. I opened the patched version in IDA Pro, waited for the initial analysis to complete, and saved the results to an IDB file. I followed the same procedure for the unpatched version. Once analysis of the unpatched version was complete, I launched the BinDiff plugin from the Edit menu. I selected the “Diff against...” button and chose the IDB file from the patched version. After about a thirty seconds of processing, BinDiff completed, resulting in four new windows inside the IDA Pro interface. These windows are labeled “Primary Unmatched”, “Secondary Unmatched”, “Matched Functions”, and the one we care about, “Changed Functions”.

Highslide JS


The Changed Functions list only contained three entries. The first was for a “GUID” function, the second was for “CTemplate::ProcessIncludeFile()”, and the third was for “HTMLEncodeLen()”. On this system, IIS had just been installed, without any patches. Based on previous analysis, I knew that the ProcessIncludeFile change was a vulnerability covered by a previous bulletin. The GUID function appeared to be used for versioning, leaving only the HTMLEncodeLen function for review. I right-clicked this function from the list and selected the “Visual Diff” menu item. This launched BinDiff's Java-based viewer – a massive upgrade over the old side-by-side text view. As you can see in the image below, six new blocks of code were added in the patched version (the red boxes on the right side).

Highslide JS


Before going any further, it makes sense to read the assembly code from the top of the function down and figure out what this function is supposed to do. The first thing I noticed is that this function is iterating through a string, passed in as an argument, one character at a time, incrementing a counter based on a variety of criteria. Characters with the values 0x3c, 0x3e, 0x26, and 0x22 are handled as special cases, as are all characters with a value greater than or equal to 0xa0. The end result of this function is to return an integer indicating the number of bytes required to store the encoded version of the string. After getting an overview of the function, I focused on the changed code blocks in the patched version. In the old code, any character greater than 0xa0 added 8 bytes to the returned size value. This makes sense when encoding a high ASCII character into an HTML entity, which would be look something like”ÿ”. However, if the input is a wide character string (16 bits per character), then the output would instead look like “￿”, two bytes larger than the maximum encoding for high ASCII characters. In the patched version of the code, a series of comparisons are made against the 16-bit value of the Unicode character in the input string. The image below shows these comparisons:

Highslide JS


The comparisons in the patched code narrowed down the problematic input to a particular range. The final range of values that is special-cased by the new code appears to be 0xd800-0xdfff. At this point, I had a good idea of what caused the bug, and what inputs this function needed to trigger it, but I still did not know how to reach this code as a remote attacker.

I switched back to IDA Pro, browsed to the HTMLEncodeLen function, and used the Control+X shortcut to bring up a list of code locations that called this function.

Highslide JS


Three different locations called the HTMLEncodeLen function. The first was the CErrInfo:WriteHTMLEncodedErrToBrowser function, the second was the Redirect method of the Response object (CResponse::Redirect), and the last was the HTMLEncode method of the Server object (CServer::HTMLEncode). A quick review of the CErrInfo and CResponse locations did not turn up any easy way to affect the input string, however, the CServer call feeds a string to this function that comes directly from the calling ASP script. To test this theory, I wrote a quick ASP script that fed Unicode input to the HTMLEncode method.
Dim buff
Dim i
For i=1 to 10000
    buff = buff & ChrW(57342) ' 0xDFFE
Next
Response.Write Server.HTMLEncode(buff)

Once the script was saved to the web root, I attached WinDbg to the ASP worker process for IIS (dllhost.exe) and accessed the ASP script in my browser. The worker process crashed almost immediately, with the following exception:
eax=34333735 ebx=00a10000 ecx=23263b32
edx=00a36e50 esi=00a36e48 edi=00000008
eip=7c91142e esp=00fcef60 ebp=00fcf180
ntdll!wcsncpy+0x99f:
7c91142e 8b39 mov edi,dword ptr [ecx]

The crash occurs inside the wcsncpy function, which is used to perform a string copy. It appears that one of the arguments to this function has been overwritten by an ASCII string. The pointer value of 0x23263b32 translates to the four byte string “2;&#”, which is part of the encoded Unicode sequence. If we can control the encoded output, we can control the bytes which are used to do the overwrite. This test script is not a realistic example, so the next challenge is finding a real-world script that is exploitable.

In order to locate ASP applications that pass user input to the HTMLEncode method, I used Google's CodeSearch web site. This site allows you to query a huge index of source code, using regular expressions as search terms. In this case, I submitted “Server\.HTMLEncode.*Request”. There were about 100 matches, including the 500-100.asp script bundled with IIS. Unfortunately, the 500-100.asp script calls HTMLEncode on the Request.Form and Request.QueryString methods, which contain the URI-encoded values, instead of the decoded Unicode values. A vulnerable use of HTMLEncode must pass the decoded values from the user, not the raw request (at least, it seems that way now).

Even though 500-100.asp doesn't look like a possibility, there are a number of common web applications that do call HTMLEncode in an exploitable way. The trick is getting the input string to be Unicode. On a typical English-language system, all strings coming from the Request.QueryString and Request.Forms methods are pulled in as ASCII and then converted to Unicode internally. To trigger this bug, the input must be treated as Unicode strings. There are two ways this can happen. The first option is if the native codepage of the server hosting the ASP script supports multi-byte characters. This configuration is common on european and asian language systems. The second option is if the ASP script itself sets the codepage property at the top of script. This declaration looks like the following:
<%@ language=”VBScript” codepage=65001 %>

This configuration is common because its required to process non-ASCII characters in web forms. Without this codepage line, a character in Japanese show up as two characters of input, not one. While looking through the CodeSearch results, I ran across a sample script bundled with the FCKeditor application. This editor is bundled with a number of popular web applications and contains a sample script that meets the requirements:
/fckeditor/_samples/asp/sampleposteddata.asp

This script starts off with the magic codepage line:
<%@ CodePage=65001 Language="VBScript"%>

Then calls HTMLEncode with the user's form input:
<td width="100%"><%=Server.HTMLEncode( Request.Form(sForm) )%></td>

I wrote a quick Ruby script to verify the crash, but ran into a problem with data encoding. Instead of using standard hex encoding for the form data, I had to use the %u format instead. The final POST request looks like:
POST /fckeditor/_samples/asp/sampleposteddata.asp HTTP/1.1
Host: target
Content-Type: application/x-www-form-urlencoded
Content-Length: 12602
x=%udffe(repeated 100 times)%u0041(repeated 2000 times)

The series of 100 0xDFFE's open up the vulnerability by transforming into sequences of #&57342; while the 0x0041's clobber the arguments used by wcsncpy with the letter “A”. After a few more minutes of debugging, I obtained the offsets to each of the overwritten pointers. Exploiting this vulnerability to execute code should be simple, but the bytes composing the different addresses must not be above 0x9f or appear in the list of special characters.
eax=00a6b6e0 ebx=00a50178 ecx=00a6c1e0
edx=41414141 esi=00a6b6d8 edi=00a50000
eip=7c910e03 esp=00fceac0 ebp=00fceb7c
ntdll!wcsncpy+0x374:
7c910e03 8902 mov dword ptr [edx],eax

The Server.HTMLEncode() method is probably not the only way to reach the exploitable function, especially if there are ways to control the output of the Response.Redirect() method as a remote user. The 500-100.asp script included with IIS may be exploitable out of the box on non-English language packs, but more testing needs to be performed to confirm that. Coverage for this vulnerability has been added to the BreakingPoint product and will be made available in the next StrikePack.

To conclude, I installed the patch on my target system, ensured that IIS was restarted, and launched my exploit again. This time, instead of a crash in wcsncpy, an access violation was triggered when the process tried to access memory beyond the end of the heap. This access violation is caught by an exception handler and an error message is displayed to the client. While this is not exploitable, it hardly seems like the correct behavior for a patched system. Digging further, I discovered what seems to be a memory disclosure flaw. If 6,000 sequences of 0xDFFE are processed by the HTMLEncode method, when exploiting the FCKeditor sample script, the encoded output contains data that has nothing to do with the input. The following ASP script returns encoded bits of process memory in the response (it may take a few requests to see it):
Dim buff
Dim i
For i=1 to 15000
    buff = buff & ChrW(57342) ' 0xDFFE
Next
Response.Write Server.HTMLEncode(buff)
Bug&Exp | Comments(3) | Trackbacks(0) | Reads(14468)
xxyyzas123
March 14, 2009 23:28
我还以为是那个内核的
doyou
March 6, 2008 10:35
怎么用啊?dog
Homepage
February 16, 2008 19:14
晕啊 ,,,我看不懂,英文...puke
Pages: 1/1 First page 1 Final page
Add a comment
Emots
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
emotemotemotemotemot
Enable HTML
Enable UBB
Enable Emots
Hidden
Nickname   Password   Optional
Site URI   Email   [Register]
               

Security code Case insensitive