Difference between revisions of "ANSI/UNICODE bug in System.Net.HttpListenerRequest"

From OWASP
Jump to: navigation, search
 
Line 3: Line 3:
 
* open ''System.Net.HttpListenerRequest'' in reflector
 
* open ''System.Net.HttpListenerRequest'' in reflector
 
* in the internal ''unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob)'' method look for this:
 
* in the internal ''unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob)'' method look for this:
**'' if ((memoryBlob.RequestBlob.CookedUrl.pFullUrl != null) && (memoryBlob.RequestBlob.CookedUrl.FullUrlLength > 0))
+
'' 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 = Marshal.PtrToStringAnsi((IntPtr) memoryBlob.RequestBlob.CookedUrl.pFullUrl, memoryBlob.RequestBlob.CookedUrl.FullUrlLength);
 
     }''
 
     }''
* this.m_CookedUrl is an private string m_CookedUrl;  
+
* ''this.m_CookedUrl'' is an private string m_CookedUrl;  
 
* and memoryBlob.RequestBlob.CookedUrl.pFullUrl is an internal unsafe ushort* pFullUrl; (i.e. a pointer).
 
* 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:
+
* If we look at the unmanaged definitions ( see [[Hooking HttpApi.dll's HttpReceiveHttpRequest]]) we have:
 +
** HttpReceiveHttpRequest ->
 +
***_HTTP_REQUEST pRequestBuffer ->
 +
**** HTTP_COOKED_URL CookedUrl ->
 +
***** PCWSTR pFullUrl
  
      HttpReceiveHttpRequest contains
+
* The problem is that unManaged version of pFullUrl is in Unicode format, where the BCL version handles it as it was ASCII
  
      _HTTP_REQUEST pRequestBuffer contains
+
* 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/"
      HTTP_COOKED_URL CookedUrl contains
+
      PCWSTR pFullUrl
+
  
# The problem is that unManaged version of pFullUrl is in Unicode format, where the BCL version handles it as it was ASCII
+
* Looking at the definition of [http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/frlrfsystemruntimeinteropservicesmarshalclassptrtostringansitopic.asp Marshal.PtrToStringAnsi] in MSDN we see that there are two definitions of Marshal.PtrToStringAnsi Method (HttpReceiveHttpRequest uses the second one)
 +
** [http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemruntimeinteropservicesmarshalclassptrtostringansitopic1.asp 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.
 +
** [http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemruntimeinteropservicesmarshalclassptrtostringansitopic2.asp public static string PtrToStringAnsi(IntPtr, int);] Allocates a [http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemstringclasstopic.asp managed String], copies a specified number of characters from an unmanaged ANSI string into it, and widens each ANSI character to Unicode.
  
# 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/"
+
*The source code of [http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemruntimeinteropservicesmarshalclassptrtostringansitopic1.asp public static string PtrToStringAnsi(IntPtr);] is (using reflector):
  
# 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)
+
[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();
 +
}
  
          o
+
* The source code of [http://msdn.microsoft.com/library/en-us/cpref/html/frlrfsystemruntimeinteropservicesmarshalclassptrtostringansitopic2.asp public static string PtrToStringAnsi(IntPtr, int);] (using reflector) is:
  
            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.
+
[MethodImpl(MethodImplOptions.InternalCall), SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.UnmanagedCode)]
          o
+
public static extern string PtrToStringAnsi(IntPtr ptr, int len);
  
            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.
+
* 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)):
  
  1.
+
* 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:
 
+
      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)
+
{
+
  
 +
<i>
 +
* PInvoke.PtrToStringAnsi()
 +
*/
 +
FCIMPL2(Object*, PtrToStringAnsi, LPVOID ptr, INT32 len)
 +
{
 
                 STRINGREF pString = NULL;
 
                 STRINGREF pString = NULL;
 
                 HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_RETURNOBJ, pString);
 
                 HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB_1(Frame::FRAME_ATTR_RETURNOBJ, pString);
Line 104: Line 80:
 
                 HELPER_METHOD_FRAME_END();
 
                 HELPER_METHOD_FRAME_END();
 
                 return OBJECTREFToObject(pString);
 
                 return OBJECTREFToObject(pString);
 +
}
 +
FCIMPLEND
 +
</i>
 +
*In summary:
 +
** The problem is in here:
  
}
+
internal unsafe HttpListenerRequest(HttpListenerContext httpContext, RequestContextBase memoryBlob)
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))
 
         if ((memoryBlob.RequestBlob.CookedUrl.pFullUrl != null) && (memoryBlob.RequestBlob.CookedUrl.FullUrlLength > 0))
Line 121: Line 94:
 
         }
 
         }
 
     ....
 
     ....
}
+
}
since it should be Marshal.PtrToStringUni and not Marshal.PtrToStringAnsi
+
**since it should be Marshal.PtrToStringUni and not Marshal.PtrToStringAnsi
  
 +
'''Note1:''' as far as I can tell m_CookedUrl is only used here:
  
Note1: as far as I can tell m_CookedUrl is only used here:
+
private System.Net.HttpListenerRequest.Uri get_RequestUri()
  
    private System.Net.HttpListenerRequest.Uri get_RequestUri()
+
<i>{
 
+
    {
+
 
         ....
 
         ....
 
         if (!string.IsNullOrEmpty(this.m_CookedUrl))
 
         if (!string.IsNullOrEmpty(this.m_CookedUrl))
Line 136: Line 108:
 
         }
 
         }
 
         ....
 
         ....
    }
+
}</i>
  
 
Which probably explains why this was not picked up by the MS .Net team (i.e. No aparent side effects)
 
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
+
'''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");
+
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.
 
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)
 

Revision as of 14:47, 21 July 2006

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 ->
      • _HTTP_REQUEST pRequestBuffer ->
        • HTTP_COOKED_URL CookedUrl ->
          • PCWSTR pFullUrl
  • The problem is that unManaged version of pFullUrl is in Unicode format, where the BCL version handles it as it was ASCII
  • 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/"
[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();
}
[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.