This blog post contains my technical notes on the Microsoft Internet Information Services (IIS) 6.0 WebDAV 'ScStoragePathFromUrl' buffer overflow vulnerability (CVE-2017-7269).

While the root cause of this bug is a simple stack overflow, the public proof of concept (PoC) leverages the bug in a creative way to take the control avoiding the different protections and security checks in the operating system.

The result is an interesting sequence of instructions where the original and attack code interleave to run remote arbitrary code.

Some context about the bug

Three weeks ago, an exploit affecting the Microsoft Internet information Services (IIS 6.0) in Windows 2003 was released publicly. The exploit, available here, is a proof of concept (PoC) running arbitrary code in x32 boxes.

Two days ago, TSB group published their fifth leak. Among the different exploits and tools published, there is an exploit codenamed "EXPLODINGCAN" abusing the same bug.

In this context, the extended support period for Windows Server 2003 ended 20 months ago, so there is no official security fix for this issue. The official MSRC recommendation is upgrading to a supported product.

To understand the technical details of the bug we need to mention two defensive measures designed to assist with memory corruption problems, overflows and so on in NT systems. They are Data Execution Prevention (DEP) and stack canaries.

The stack canary approach in NT systems is also known as "GS check" or "/GS security check". The name comes from the compiler switch used to configure it. The "GS check" low-level implementation is described in detail here.

Both defensive measures are in place but the exploit is able to avoid them in a creative way.

The bug

As stated by the CVE-2017-7269 entry, there is a buffer overflow in the WebDAV service' ScStoragePathFromUrl function shipping with Internet Information Services (IIS) 6.0 in Microsoft Windows Server 2003 R2.

The overflow allows remote attackers to execute arbitrary code via a long header beginning with "If: <http://" in a PROPFIND request.

The attack vector

The vulnerable code is located at the bottom of the function ScStoragePathFromUrl:

    v21 = v35;
    v22 = v16;
    v23 = 2 * v16;
    v24 = (unsigned int)(2 * v16) >> 2;
    qmemcpy(v35, &v40, 4 * v24);
    v26 = (char *)&v40 + 4 * v24;
    v25 = (char *)&v21[2 * v24];
    LOBYTE(v24) = v23;
    v27 = v40.cchMatchingURL;
    qmemcpy(v25, v26, v24 & 3);
    v28 = v6 - v27;
    v29 = &Str[v27];
    v30 = v35;
    qmemcpy(&v35[v22], v29, 2 * v28 + 2);

This function, ScStoragePathFromUrl, translates the file path coming in the URL to a valid physical path. This transformation moves bytes between two buffers (source and target) withouth the proper bound limit checking. A previous mistake allocating the target buffer size as the number of characters instead of the number of bytes is the root cause of the bug. ScStoragePathFromUrl assumes the target buffer is big enough in this point.

So the bug is a classic stack overflow and the exploiter should be able to take control overflowing the return address with a simple hit, etc. but the problem here is the GS check mentioned previously. In the function allocating the vulnerable buffer there is a stack canary protecting the integrity of the stack. If the exploit overwrites this stack canary the security check will not pass and the program will end.

The approach of this exploit to overcome this canary check is looking for a logic code flow running several times over the vulnerable code and abusing it to achieve two goals:

  • Moving the payload to the heap to avoid corrupting the stack with a long input, passing the canary check and, at the same time, keeping safe the integrity of this payload among function calls.
  • Overwriting relevant stack variables of the functions allocating the target buffers in order to take the control of the instruction pointer and run arbitrary code.

The exploit abuses the following code flow:


-- w3isapi!ProcessIsapiRequest
----- httpext!DwDavFSExtensionProc
-------- httpext!CDAVExt:DwMain
----------- httpext!DAVPropFind
-------------- httpext!CPropFindRequest::Execute
----------------- httpext!HrCheckStateHeaders
-------------------- httpext!HrCheckIfHeader
------------------------ httpext!CMethUtil::ScStoragePathFromUrl
---------------------------  httpext!ScStoragePathFromUrl
------------------------ httpext!CMethUtil::ScStoragePathFromUrl
---------------------------  httpext!ScStoragePathFromUrl
...
----------------- httpext!FGetLockHandle
-------------------- httpext!CParseLockTokenHeader::HrGetLockIdForPath
----------------------- httpext!CMethUtil::ScStoragePathFromUrl
-------------------------- httpext!ScStoragePathFromUrl
----------------------------- httpext!ScStripAndCheckHttpPrefix
----------------------- httpext!CMethUtil::ScStoragePathFromUrl
-------------------------- httpext!ScStoragePathFromUrl

HrCheckIfHeader and HrGetLockIdForPath allocate the vulnerable buffers and call on ScStoragePathFromUrl. ScStoragePathFromUrl is able to overwrite the stack variables in those two functions when the bug is triggered.

The input string used by the exploit to trigger the vulnerability is a combination of two urls. They are identified in the exploit code as 'http://localhost/aaaaaaa' and 'http://localhost/bbbbbbb'. We will name those two substrings 'String_A' and 'String_B'.

HrCheckIfHeader and HrGetLockIdForPath handle the input string in two runs. The first run handles String_A and the second run handles String_B. String_B will reuse the target buffer set up by String_A.

HrCheckIfHeader will be abused by String_A to copy the payload coming in String_B to the heap.

HrGetLockIdForPath will be abused by String_A. It will use the String_B handling flow to hijack the instruction pointer and take the control.

In both cases the involved buffers live in a class called CStackBuffer. CStackBuffer objects are allocated in the stack as mentioned.

This is the CStackBuffer constructor...


int __thiscall CStackBuffer::CStackBuffer(int this, unsigned int a2)
{
  int v2; // esi@1

  v2 = this;
  *(_DWORD *)(this + 260) = 0;
  *(_DWORD *)(this + 264) = 0;
  CStackBuffer::resize(this, a2);
  return v2;
}

and this is the CStackBuffer resize method.


int __thiscall CStackBuffer::resize(int this, unsigned int a2)
{
  unsigned __int32 v2; // edi@1
  int v3; // esi@1
  int v4; // ecx@3
  void *v5; // eax@6

  v2 = a2;
  v3 = this;
  if ( a2 <= 0x104 )
    v2 = 260;
  v4 = *(_DWORD *)(this + 260);
  if ( *(_DWORD *)(v3 + 260) >> 2 < v2 )
  {
    if ( v4 & 1 || v2 > 0x104 )
    {
      CStackBuffer::release(v3);
      v5 = ExAlloc(v2);
      *(_DWORD *)(v3 + 260) |= 1u;
    }
    else
    {
      v5 = (void *)v3;
    }
    *(_DWORD *)(v3 + 264) = v5;
    *(_DWORD *)(v3 + 260) = 4 * v2 | *(_DWORD *)(v3 + 260) & 3;
  }
  *(_DWORD *)(v3 + 260) |= 2u;
  return *(_DWORD *)(v3 + 264);
}

This class works as a data container switching between a stack buffer or a heap buffer with a threshold of 260 (0x104) bytes.

It uses its allocated stack buffer of 260 bytes with inputs under 261 bytes, but handling inputs over 260 bytes it will use the heap buffer. As seen in the pseudocode, "this + 264" will hold a buffer pointer and "this + 260" will hold the encoded size of the buffer. Let's call these variables 'current_buffer' and 'current_buffer_size'.

If we pay attention to the offset of the current_buffer variable (this+260) we confirm the previous 260 bytes belong to the buffer used by CStackBuffer with inputs below 261 bytes. The class variables for CStackBuffer have the following layout:


UCHAR buffer[260];
UINT current_buffer_size;
UINT current_buffer;

So any buffer overflow will overwrite current_buffer_size and current_buffer variables. This is the way how the exploit is able to take the control of the heap buffer and its size.

The fundamental idea behind of this exploit is abusing the functions' stack allocating CStackBuffer objects. In functions using this kind of objects, the exploit has the chance to overwrite current_buffer_size and current_buffer with arbitrary values and then moving long inputs in the heap. This moving of bytes will not corrupt the stack.

This is the technique used to exploit HrCheckIfHeader. This is the code abused:


  CStackBuffer::CStackBuffer((int)&v28, 0x104u);
  ...
  v6 = IFITER::PszNextToken((int)&v20, 0);
  while ( 2 )
  {
    v7 = v6;
    if ( v6 )
    {
      CStackBuffer::CStackBuffer((int)&v31, 0x104u);
      ...
      v27 = v29 >> 3;
      v5 = CMethUtil::ScStoragePathFromUrl(a1, v32, Str, &v27);
      if ( v5 == 1 )
      {
        if ( !CStackBuffer::resize((int)&v28, v27) )
        {
LABEL_35:
          LOBYTE(v34) = 1;
          CStackBuffer::release((int)&v31);
          v5 = -2147024882;
          goto LABEL_39;
        }
        v5 = CMethUtil::ScStoragePathFromUrl(a1, v32, Str, &v27);
      }
      ...

It allocates two CStackBuffer objects in the stack and it calls on ScStoragePathFromUrl twice. Both calls reuse the target buffer 'Str', the effective buffer being used by CStackBuffer depending on the input length. As mentioned it will point on the stack or the heap.

With the first run it handles the String_A. ScStoragePathFromUrl is called the first time and it returns 1. In v27, the length, will save the new length (0xaa = 170). 170 is below of 261 so the resize method will not use the heap and it calls on ScStoragePathFromUrl a second time.

The second ScStoragePathFromUrl will overflow and it will overwrite current_buffer_size with 0x02020202 and current_buffer with 0x680312c0

With the second run it handles the String_B. In this case the first ScStoragePathFromUrl handles the whole input. current_buffer_size is large enough and the data will move on the address 0x680312c0.

The genuine code continues execution and it hits HrGetLockIdForPath. In this function the exploit abuses the following code:


    ...
    v26 = v30 >> 3;
    v23 = CMethUtil::ScStoragePathFromUrl(v12, v28, Str, &v26);
    if ( v23 == 1 )
    {
      if ( !CStackBuffer::resize((int)&v29, v26) )
        goto LABEL_18;
      v23 = CMethUtil::ScStoragePathFromUrl(*v11, v28, Str, &v26);
    }
    ...

It is code very similar to the previous one although the goal of the exploitation is very different.

The code will handle String_A as expected and it will overwrite a CParseLockTokenHeader variable (v7) living in the FGetLockHandle stack (the parent function of HrGetLockIdForPath). With this variable under control the exploit is able to use a crafted IEcb object in ScStripAndCheckHttpPrefix to hijack the instruction pointer.

So the goal of overwriting this variable is taking the control via ScStripAndCheckHttpPrefix with the first call on ScStoragePathFromUrl for the String_B handling. The code flow under abuse follows...


int __stdcall FGetLockHandle(struct CMethUtil *a1,
                             wchar_t *Str,
			     unsigned __int32 a3,
			     const unsigned __int16 *a4,
			     struct auto_ref_handle *a5)
{
  signed int v5; // eax@1
  int result; // eax@2
  char v7; // [sp+0h] [bp-54h]@1
  union _LARGE_INTEGER v8; // [sp+40h] [bp-14h]@1
  int v9; // [sp+50h] [bp-4h]@1

  CParseLockTokenHeader::CParseLockTokenHeader((CParseLockTokenHeader *)&v7,
                                               a1,
					       a4);
  v9 = 0;
  CParseLockTokenHeader::SetPaths((CParseLockTokenHeader *)&v7,
                                                            Str,
							    0);
  v5 = CParseLockTokenHeader::HrGetLockIdForPath((CParseLockTokenHeader *)&v7,
                                                  Str,
						  a3,
						  &v8,
						  0);


                       .....................


signed int __thiscall CParseLockTokenHeader::HrGetLockIdForPath(
                                                  CParseLockTokenHeader *this,
                                                  const unsigned __int16 *a2,
						  unsigned __int32 a3,
						  union _LARGE_INTEGER *a4,
						  int a5)
{
  ...
  union _LARGE_INTEGER v22; // [sp+2Ch] [bp-240h]@1
  ...
  v22.HighPart = (LONG)this;
  ...
    v11 = (CMethUtil **)v22.HighPart;
    v12 = *(CMethUtil **)v22.HighPart;
    v26 = v30 >> 3;
    v23 = CMethUtil::ScStoragePathFromUrl(v12, v28, Str, &v26);
    if ( v23 == 1 )
    {
      if ( !CStackBuffer::resize((int)&v29, v26) )
        goto LABEL_18;
      v23 = CMethUtil::ScStoragePathFromUrl(*v11, v28, Str, &v26);
    }


                       .....................


void __fastcall ScStoragePathFromUrl(const struct IEcb *a1,
                                     const unsigned __int16 *a2,
				     unsigned __int16 *a3,
				     unsigned int *a4,
				     struct CVRoot **a5)
{
  IEcbBase *v5; // esi@1
  ...
  int v45; // [sp+460h] [bp-4h]@15

  v35 = a3;
  v5 = a1;
  Str = (wchar_t *)a2;
  v37 = a1;
  v34 = a4;
  v33 = a5;
  if ( ScStripAndCheckHttpPrefix(a1, (const unsigned __int16 **)&Str) < 0 )
    return;


                       .....................


signed int __fastcall ScStripAndCheckHttpPrefix(const struct IEcb *a1,
                                                const unsigned __int16 **a2)
{
  const unsigned __int16 *v2; // esi@1
  ...
  wchar_t *Str1; // [sp+14h] [bp-4h]@1

  v10 = 0;
  v2 = *a2;
  v3 = a1;
  v9 = a2;
  v4 = (*(int (__stdcall **)(wchar_t **))(*(_DWORD *)a1 + 36))(&Str1);

The exploit overwrites v7 with the value 0x680313c0. This address falls into the payload moved in the heap previously so this content is also under control by the exploit.

This content provides the minimal IEcb object layout to take control through a 'call [eax+0x24]'.

The payload

The payload is not so interesting as the attack vector although it is also carefully crafted. It uses 'return-oriented programming' (ROP) to avoid the DEP protection. The exploit runs code fragments located in the rsaenh.dll module.

The ROP chain pivots the stack and uses the 'SharedUserData' technique to enable execution permissions on the payload.

The new permissions...

Related to the memory permissions, the previous permissions are enough to execute code in my testing box (Windows 2003 R2 sp2) but the exploit asks for new permissions explicitly.

I guess this behaviour is a symptom the payload is designed to run over other configurations where this step could be required. It makes the payload more portable.

After that, it jumps over the alphanumeric shellcode.

The alphanumeric shellcode is executable code at the beginning. The shellcode decodes itself to recovers its original form.

After this decoding, the default shellcode runs the "calc.exe" application under the NETWORK SERVICE credentials.

Update 2017/06/13

Microsoft releases additional updates for older platforms to protect against potential nation-state activity.

Comments