ANSI/UNICODE bug in System.Net.HttpListenerRequest

From OWASP
Revision as of 14:38, 21 July 2006 by Dinis.cruz (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

While testing what was the best function to hook in HttpListenerRequest (see Hooking HttpApi.dll's HttpReceiveHttpRequest) I found what I think is a bug in the 2.0 method System.Net.HttpListenerRequest which belongs to the Platform SDK: HTTP API]:

  • open System.Net.HttpListenerRequest in reflector
  • in the internal unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob) method look for this:
    • if ((memoryBlob.RequestBlob.CookedUrl.pFullUrl != null) && (memoryBlob.RequestBlob.CookedUrl.FullUrlLength > 0))
   {
     this.m_CookedUrl = Marshal.PtrToStringAnsi((IntPtr) memoryBlob.RequestBlob.CookedUrl.pFullUrl, memoryBlob.RequestBlob.CookedUrl.FullUrlLength);
   }
  • this.m_CookedUrl is an private string m_CookedUrl;
  • and memoryBlob.RequestBlob.CookedUrl.pFullUrl is an internal unsafe ushort* pFullUrl; (i.e. a pointer).
  • If we look at the unmanaged definitions ( see Hooking HttpApi.dll's HttpReceiveHttpRequest) we have:
     HttpReceiveHttpRequest contains
     _HTTP_REQUEST pRequestBuffer contains
     HTTP_COOKED_URL CookedUrl contains
     PCWSTR pFullUrl
  1. The problem is that unManaged version of pFullUrl is in Unicode format, where the BCL version handles it as it was ASCII
  1. I noticed this problem because System.Net.HttpListenerRequest.m_cookedUrl, had a value of "h\0t\0t\0p\0:\0/\0/\0l\0o\0c\0a\0l\0h\0o\0s\0t\0:\08\00\09\00\0/" which if you remove the \0 is "http://localhost:8090/"
  1. Looking at the definition of Marshal.PtrToStringAnsi in MSDN we see that there are two definitions of Marshal.PtrToStringAnsi Method (HttpReceiveHttpRequest uses the second one)
         o
           public static string PtrToStringAnsi(IntPtr); Copies all characters up to the first null from an unmanaged ANSI string to a managed String. Widens each ANSI character to Unicode.
         o
           public static string PtrToStringAnsi(IntPtr, int); Allocates a managed String, copies a specified number of characters from an unmanaged ANSI string into it, and widens each ANSI character to Unicode.
  1.
     The source code of public static string PtrToStringAnsi(IntPtr); is
         o
           using reflector:
           [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]

public static string PtrToStringAnsi(IntPtr ptr) { if (Win32Native.NULL == ptr) { return null; } if (Marshal.IsWin32Atom(ptr)) { return null; } int num1 = Win32Native.lstrlenA(ptr); if (num1 == 0) { return string.Empty; } StringBuilder builder1 = new StringBuilder(num1); Win32Native.CopyMemoryAnsi(builder1, ptr, new IntPtr(1 + num1)); return builder1.ToString(); }

        1.
           The source code of public static string PtrToStringAnsi(IntPtr, int);
               +
                 using reflector
                 [MethodImpl(MethodImplOptions.InternalCall), SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
                 public static extern string PtrToStringAnsi(IntPtr ptr, int len);

we see that it is an internalCall method ("MethodImplOptions.InternalCall" is used to declare a method in managed code that has no managed implementation. The implementation (unmanaged) is supplied by the runtime itself (CLR)):

               +
                 Since we don't have the source code of the CLR and the 2.0 version of Rotor, I had to look at Rotor's version 1.1 where I found (using CodeScoping's cool feature for searching text in files:) ) inside (\sscli\clr\src\vm\comndirect.cpp) this:

/************************************************************************

  • PInvoke.PtrToStringAnsi()
  • /

FCIMPL2(Object*, PtrToStringAnsi, LPVOID ptr, INT32 len) {

               STRINGREF pString = NULL;
               HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_RETURNOBJ, pString);
               //-[autocvtpro]-------------------------------------------------------
               THROWSCOMPLUSEXCEPTION();
               if (ptr == NULL)
                   COMPlusThrowArgumentNull(L"ptr");
               if (len < 0)
                   COMPlusThrowNonLocalized(kArgumentException, L"len");
               int nwc = 0;
               if (len != 0) {
                   nwc = MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)(ptr),len,NULL,0);
                   if (nwc == 0)COMPlusThrow(kArgumentException, IDS_UNI2ANSI_FAILURE);
               }
               pString = COMString::NewString(nwc);
               MultiByteToWideChar(CP_ACP,MB_PRECOMPOSED,(LPCSTR)(ptr),len,pString->GetBuffer(),nwc);
               //-[autocvtepi]-------------------------------------------------------
               HELPER_METHOD_FRAME_END();
               return OBJECTREFToObject(pString);

} FCIMPLEND

In summary:

The problem is in here:

internal unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob)

{

   ....
       if ((memoryBlob.RequestBlob.CookedUrl.pFullUrl != null) && (memoryBlob.RequestBlob.CookedUrl.FullUrlLength > 0))
       {
           this.m_CookedUrl = Marshal.PtrToStringAnsi((IntPtr) memoryBlob.RequestBlob.CookedUrl.pFullUrl, memoryBlob.RequestBlob.CookedUrl.FullUrlLength)  
       }
   ....

} since it should be Marshal.PtrToStringUni and not Marshal.PtrToStringAnsi


Note1: as far as I can tell m_CookedUrl is only used here:

   private System.Net.HttpListenerRequest.Uri get_RequestUri()
   {
       ....
       if (!string.IsNullOrEmpty(this.m_CookedUrl))
       {
           flag1 = Uri.TryCreate(this.m_CookedUrl, UriKind.Absolute, out this.m_RequestUri);
       }
       ....
   }

Which probably explains why this was not picked up by the MS .Net team (i.e. No aparent side effects)

Note2: using the detoured HttpReceiveHttpRequest I changed the contents of (*pRequestBuffer).CookedUrl.pFullUrl to

StringCbPrintfA((STRSAFE_LPSTR)(*pRequestBuffer).CookedUrl.pFullUrl,30,"http://localhost:8090/Test.aspx");

and I got “http://localhost:8090/Test.aspx*&(&(^(^(*&(*&(*&” (i.e. The original ANSI string + strlen(original ANSI string) chars) in System.Net.HttpListenerRequest.m_CookedUrl (which shows that the original assumption was that (*pRequestBuffer).CookedUrl.pFullUrl should have an ANSI string.

Note 3: I'm calling this a bug because I don't think that this is exploitable (now if the bug was in converting an Unicode string into an ANSI string (using the full number of bytes in the Unicode string), them this could be exploitable (even then it would depend of what values were stored right after the target object)