This site is the archived OWASP Foundation Wiki and is no longer accepting Account Requests.
To view the new OWASP Foundation website, please visit https://owasp.org
ANSI/UNICODE bug in System.Net.HttpListenerRequest
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
- HTTP_COOKED_URL CookedUrl ->
- _HTTP_REQUEST pRequestBuffer ->
- HttpReceiveHttpRequest ->
- 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/"
- 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)
- 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.
- 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.
- The source code of public static string PtrToStringAnsi(IntPtr); is (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(); }
- The source code of public static string PtrToStringAnsi(IntPtr, int); (using reflector) is:
[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.