在 WebBrowser 中检索 HttpOnly 会话 Cookie






4.73/5 (12投票s)
HttpOnly 是 IE 6 SP1、Opera、Safari 和 KDE 中的一项功能,允许设置仅通过 HTTP 标头发送且无法通过客户端脚本访问的 Cookie。

引言
为了帮助减轻跨站点脚本攻击的风险,微软 Internet Explorer 6 SP1 引入了一项新功能。此功能是 Cookie 的一个新属性,它阻止通过客户端脚本访问 Cookie。具有此属性的 Cookie 称为 HttpOnly Cookie。
演示程序在启动时访问网页 https://codeproject.org.cn/index.aspx,并获取响应 HTTP 标头:
HTTP/1.1 200 OK
Cache-Control: private
Date: Fri, 31 Jul 2009 08:38:03 GMT
Content-Type: text/html; charset=utf-8
Server: Microsoft-IIS/6.0
X-Powered-By: ASP.NET
X-AspNet-Version: 2.0.50727
Set-Cookie: SessionGUID=03e40347-534c-4160-8936-19745e6dae20; path=/
Set-Cookie: ASP.NET_SessionId=beegcn2fu2pmjjeyvdu1uzrv; path=/; HttpOnly
Transfer-Encoding: chunked
Cookie ASP.Net_SessionId 被标记为 HttpOnly,无法通过 IHTMLDocument2::get_cookie
方法获取。
背景
WebBrowser (mshtml.dll) 通过调用 WININET.dll 暴露的方法访问 HTTP Web 服务器。

挂钩 WININET.DLL 暴露的方法可以与发送到服务器的每个请求进行交互,包括 AJAX
请求!
可能的调用序列如下所示:
1=> InternetOpen
2=> InternetConnect
3=> HttpOpenRequest
// .............
X=> InternetCloseHandle
通过挂钩这些方法,我们可以检测发送到 Web 服务器的每个请求,并在每个请求之前/之后注入我们的代码。为什么不使用 WINSOCK?由于前者封装了 HTTP 协议,因此挂钩 WININET.DLL 暴露的方法比挂钩 WINSOCK
方法更容易。
挂钩有两种不同的方式,即内联挂钩和例程挂钩。内联挂钩几乎适用于所有情况,但是例程挂钩更健壮。该示例通过修改 IAT(导入地址表)来实现它,IAT 是例程挂钩的一种。
如果您需要有关 IAT 挂钩的更多详细信息,请参阅本文作为参考。
代码片段
// required header files and library files
#include <WinInet.h>
#pragma comment( lib, "WinInet.lib")
#include <Dbghelp.h>
#include <DelayImp.h>
#pragma comment( lib, "Dbghelp.lib")
#include <Psapi.h>
#pragma comment( lib, "Psapi.lib")
// The following macro can be found in IE8 SDK
#ifndef INTERNET_COOKIE_NON_SCRIPT
#define INTERNET_COOKIE_NON_SCRIPT 0x00001000
#endif
#ifndef INTERNET_COOKIE_HTTPONLY
#define INTERNET_COOKIE_HTTPONLY 0x00002000
#endif
// The following macro can be found in DDK/WDK header files
typedef struct _STRING {
USHORT Length;
USHORT MaximumLength;
PCHAR Buffer;
} ANSI_STRING, *PANSI_STRING;
typedef struct _LSA_UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
}LSA_UNICODE_STRING, *PLSA_UNICODE_STRING, UNICODE_STRING, *PUNICODE_STRING;
typedef LONG NTSTATUS;
#ifndef STATUS_SUCCESS
#define STATUS_SUCCESS ((NTSTATUS)0x00000000L)
#endif
// The function pointer statements for those function to hook
typedef NTSTATUS (WINAPI* PFN_LdrGetProcedureAddress)
(IN HMODULE ModuleHandle, IN PANSI_STRING FunctionName OPTIONAL,
IN WORD Oridinal OPTIONAL, OUT PVOID *FunctionAddress );
typedef NTSTATUS (WINAPI* PFN_LdrLoadDll)(IN PWCHAR PathToFile OPTIONAL,
IN ULONG Flags OPTIONAL, IN PUNICODE_STRING ModuleFileName,
OUT PHANDLE ModuleHandle);
typedef BOOL (WINAPI* PFN_HttpSendRequestA)(HINTERNET hRequest,
LPCSTR lpszHeaders, DWORD dwHeadersLength,
LPVOID lpOptional, DWORD dwOptionalLength );
typedef BOOL (WINAPI* PFN_HttpSendRequestW)
(HINTERNET hRequest, LPCWSTR lpszHeaders,
DWORD dwHeadersLength, LPVOID lpOptional, DWORD dwOptionalLength );
typedef BOOL (WINAPI* PFN_HttpSendRequestExA)
( __in HINTERNET hRequest, __in_opt LPINTERNET_BUFFERSA lpBuffersIn,
__out_opt LPINTERNET_BUFFERSA lpBuffersOut, __in DWORD dwFlags,
__in_opt DWORD_PTR dwContext);
typedef BOOL (WINAPI* PFN_HttpSendRequestExW)
( __in HINTERNET hRequest, __in_opt LPINTERNET_BUFFERSW lpBuffersIn,
__out_opt LPINTERNET_BUFFERSW lpBuffersOut, __in DWORD dwFlags,
__in_opt DWORD_PTR dwContext);
typedef BOOL (WINAPI* PFN_HttpEndRequestA)( __in HINTERNET hRequest,
__out_opt LPINTERNET_BUFFERSA lpBuffersOut, __in DWORD dwFlags,
__in_opt DWORD_PTR dwContext);
typedef BOOL (WINAPI* PFN_HttpEndRequestW)( __in HINTERNET hRequest,
__out_opt LPINTERNET_BUFFERSW lpBuffersOut, __in DWORD dwFlags,
__in_opt DWORD_PTR dwContext);
typedef HINTERNET (WINAPI* PFN_HttpOpenRequestA)
(__in HINTERNET hConnect,__in_opt LPCSTR lpszVerb,
__in_opt LPCSTR lpszObjectName, __in_opt LPCSTR lpszVersion,
__in_opt LPCSTR lpszReferrer, __in_z_opt LPCSTR FAR * lplpszAcceptTypes,
__in DWORD dwFlags, __in_opt DWORD_PTR dwContext);
typedef HINTERNET (WINAPI* PFN_HttpOpenRequestW)
(__in HINTERNET hConnect,__in_opt LPCWSTR lpszVerb,
__in_opt LPCWSTR lpszObjectName,__in_opt LPCWSTR lpszVersion,
__in_opt LPCWSTR lpszReferrer,__in_z_opt LPCWSTR FAR * lplpszAcceptTypes,
__in DWORD dwFlags, __in_opt DWORD_PTR dwContext);
typedef HINTERNET (WINAPI* PFN_InternetConnectA)
(__in HINTERNET hInternet,__in LPCSTR lpszServerName,
__in INTERNET_PORT nServerPort,__in_opt LPCSTR lpszUserName,
__in_opt LPCSTR lpszPassword,__in DWORD dwService,__in DWORD dwFlags,
__in_opt DWORD_PTR dwContext);
typedef HINTERNET (WINAPI* PFN_InternetConnectW)
(__in HINTERNET hInternet,__in LPCWSTR lpszServerName,
__in INTERNET_PORT nServerPort,__in_opt LPCWSTR lpszUserName,
__in_opt LPCWSTR lpszPassword,__in DWORD dwService,__in DWORD dwFlags,
__in_opt DWORD_PTR dwContext);
typedef BOOL (WINAPI* PFN_HttpAddRequestHeadersA)
(__in HINTERNET hRequest,__in_ecount(dwHeadersLength) LPCSTR lpszHeaders,
__in DWORD dwHeadersLength,__in DWORD dwModifiers);
typedef BOOL (WINAPI* PFN_HttpAddRequestHeadersW)
(__in HINTERNET hRequest,__in_ecount(dwHeadersLength) LPCWSTR lpszHeaders,
__in DWORD dwHeadersLength,__in DWORD dwModifiers);
typedef HINTERNET (WINAPI* PFN_InternetOpenUrlA)
(HINTERNET hInternet,LPCSTR lpszUrl, LPCSTR lpszHeaders,
DWORD dwHeadersLength, DWORD dwFlags, DWORD_PTR dwContext);
typedef HINTERNET (WINAPI* PFN_InternetOpenUrlW)
(HINTERNET hInternet,LPCWSTR lpszUrl,LPCWSTR lpszHeaders,
DWORD dwHeadersLength,DWORD dwFlags,DWORD_PTR dwContext);
typedef HINTERNET (WINAPI* PFN_InternetOpenA)
( LPCSTR lpszAgent, DWORD dwAccessType, LPCSTR lpszProxy,
LPCSTR lpszProxyBypass, DWORD dwFlags );
typedef HINTERNET (WINAPI* PFN_InternetOpenW)
( LPCWSTR lpszAgent, DWORD dwAccessType, LPCWSTR lpszProxy,
LPCWSTR lpszProxyBypass, DWORD dwFlags );
typedef BOOL (WINAPI* PFN_InternetCloseHandle)(__in HINTERNET hInternet);
typedef BOOL (WINAPI* PFN_InternetSetCookieA)
(LPCSTR lpszUrl, LPCSTR lpszCookieName, LPCSTR lpszCookieData );
typedef BOOL (WINAPI* PFN_InternetSetCookieW)
( LPCWSTR lpszUrl, LPCWSTR lpszCookieName, LPCWSTR lpszCookieData);
typedef DWORD (WINAPI* PFN_InternetSetCookieExA)
(LPCSTR lpszUrl, LPCSTR lpszCookieName,
LPCSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved);
typedef DWORD (WINAPI* PFN_InternetSetCookieExW)
( LPCWSTR lpszUrl, LPCWSTR lpszCookieName,
LPCWSTR lpszCookieData, DWORD dwFlags, DWORD_PTR dwReserved );
class CWininetHook
{
public:
CWininetHook(void);
~CWininetHook(void);
private:
// variables to store the original function address
static PFN_LdrLoadDll s_pfnLdrLoadDll;
static PFN_LdrGetProcedureAddress s_pfnLdrGetProcedureAddress;
static PFN_HttpSendRequestA s_pfnHttpSendRequestA;
static PFN_HttpSendRequestW s_pfnHttpSendRequestW;
static PFN_HttpSendRequestExA s_pfnHttpSendRequestExA;
static PFN_HttpSendRequestExW s_pfnHttpSendRequestExW;
static PFN_HttpEndRequestA s_pfnHttpEndRequestA;
static PFN_HttpEndRequestW s_pfnHttpEndRequestW;
static PFN_HttpOpenRequestA s_pfnHttpOpenRequestA;
static PFN_HttpOpenRequestW s_pfnHttpOpenRequestW;
static PFN_InternetConnectA s_pfnInternetConnectA;
static PFN_InternetConnectW s_pfnInternetConnectW;
static PFN_HttpAddRequestHeadersA s_pfnHttpAddRequestHeadersA;
static PFN_HttpAddRequestHeadersW s_pfnHttpAddRequestHeadersW;
static PFN_InternetOpenUrlA s_pfnInternetOpenUrlA;
static PFN_InternetOpenUrlW s_pfnInternetOpenUrlW;
static PFN_InternetOpenA s_pfnInternetOpenA;
static PFN_InternetOpenW s_pfnInternetOpenW;
static PFN_InternetCloseHandle s_pfnInternetCloseHandle;
static PFN_InternetSetCookieA s_pfnInternetSetCookieA;
static PFN_InternetSetCookieW s_pfnInternetSetCookieW;
static PFN_InternetSetCookieExA s_pfnInternetSetCookieExA;
static PFN_InternetSetCookieExW s_pfnInternetSetCookieExW;
};
static CWininetHook g_oHook;
// initialize the static variables
PFN_LdrLoadDll CWininetHook::s_pfnLdrLoadDll = NULL;
PFN_LdrGetProcedureAddress CWininetHook::s_pfnLdrGetProcedureAddress = NULL;
// save the original address
PFN_HttpSendRequestA CWininetHook::s_pfnHttpSendRequestA = HttpSendRequestA;
PFN_HttpSendRequestW CWininetHook::s_pfnHttpSendRequestW = HttpSendRequestW;
PFN_HttpAddRequestHeadersA CWininetHook::s_pfnHttpAddRequestHeadersA =
HttpAddRequestHeadersA;
PFN_HttpAddRequestHeadersW CWininetHook::s_pfnHttpAddRequestHeadersW =
HttpAddRequestHeadersW;
PFN_HttpSendRequestExA CWininetHook::s_pfnHttpSendRequestExA = HttpSendRequestExA;
PFN_HttpSendRequestExW CWininetHook::s_pfnHttpSendRequestExW = HttpSendRequestExW;
PFN_HttpEndRequestA CWininetHook::s_pfnHttpEndRequestA = HttpEndRequestA;
PFN_HttpEndRequestW CWininetHook::s_pfnHttpEndRequestW = HttpEndRequestW;
PFN_HttpOpenRequestA CWininetHook::s_pfnHttpOpenRequestA = HttpOpenRequestA;
PFN_HttpOpenRequestW CWininetHook::s_pfnHttpOpenRequestW = HttpOpenRequestW;
PFN_InternetConnectA CWininetHook::s_pfnInternetConnectA = InternetConnectA;
PFN_InternetConnectW CWininetHook::s_pfnInternetConnectW = InternetConnectW;
PFN_InternetOpenUrlA CWininetHook::s_pfnInternetOpenUrlA = InternetOpenUrlA;
PFN_InternetOpenUrlW CWininetHook::s_pfnInternetOpenUrlW = InternetOpenUrlW;
PFN_InternetOpenA CWininetHook::s_pfnInternetOpenA = InternetOpenA;
PFN_InternetOpenW CWininetHook::s_pfnInternetOpenW = InternetOpenW;
PFN_InternetCloseHandle CWininetHook::s_pfnInternetCloseHandle = InternetCloseHandle;
PFN_InternetSetCookieA CWininetHook::s_pfnInternetSetCookieA = InternetSetCookieA;
PFN_InternetSetCookieW CWininetHook::s_pfnInternetSetCookieW = InternetSetCookieW;
PFN_InternetSetCookieExA CWininetHook::s_pfnInternetSetCookieExA = InternetSetCookieExA;
PFN_InternetSetCookieExW CWininetHook::s_pfnInternetSetCookieExW = InternetSetCookieExW;
// Enumerate all the loaded modules in the current process,
// and search for the IAT to be processed
void CWininetHook::ReplaceIATEntryForAll(LPCSTR lpszDllName,
LPVOID pfnCurrent, LPVOID pfnNew)
{
HMODULE hMods[1024] = {0};
DWORD cbNeeded;
HANDLE hProcess = ::GetCurrentProcess();
if( ::EnumProcessModules( hProcess, hMods, sizeof(hMods), &cbNeeded))
{
for ( UINT i = 0; i < (cbNeeded / sizeof(HMODULE)); i++ )
{
ReplaceIATEntryInImageImportTable( hMods[i]
, lpszDllName
, pfnCurrent
, pfnNew
);
}
}
}
// search in IMAGE_IMPORT_DESCRIPTOR for the special module
BOOL CWininetHook::ReplaceIATEntryInImageImportTable( HANDLE hBaseAddress
, LPCSTR lpszDllName
, LPVOID pfnCurrent
, LPVOID pfnNew
)
{
ASSERT(hBaseAddress && lpszDllName && pfnCurrent && pfnNew );
// retrieve IMAGE_IMPORT_DESCRIPTOR
DWORD dwSize = 0;
PIMAGE_SECTION_HEADER pFoundHeader = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImgImportDescriptor
= (PIMAGE_IMPORT_DESCRIPTOR)ImageDirectoryEntryToDataEx( hBaseAddress
, TRUE
, IMAGE_DIRECTORY_ENTRY_IMPORT
, &dwSize
, &pFoundHeader
);
if( pImgImportDescriptor == NULL ){ return FALSE; }
while (pImgImportDescriptor->Name)
{
if ( _strcmpi((CHAR*)((PBYTE)hBaseAddress+pImgImportDescriptor->Name),
lpszDllName) == 0 )
{
break; // found
}
++pImgImportDescriptor;
}
// NOTE:
// If the special module can not be found in IMAGE_DIRECTORY_ENTRY_IMPORT
// Then should try to search it in IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
if( !pImgImportDescriptor->Name )
return ReplaceIATEntryInDelayImageImportTable
( hBaseAddress, lpszDllName, pfnCurrent, pfnNew);
// retrieve IAT
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)
(((LPBYTE)hBaseAddress) + pImgImportDescriptor->FirstThunk);
// enumerate functions in the IAT
while(pThunk->u1.Function)
{
PDWORD lpAddr = (PDWORD)&(pThunk->u1.Function);
if(*lpAddr == (DWORD)pfnCurrent)
{
// modify the address
::WriteProcessMemory(::GetCurrentProcess()
, lpAddr
, &pfnNew
, sizeof(DWORD)
, NULL
);
return TRUE;
}
pThunk++;
}
return FALSE;
}
对于延迟加载的 DLL,请在 IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
中搜索。
// search in IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT for the special module
BOOL CWininetHook::ReplaceIATEntryInDelayImageImportTable( HANDLE hBaseAddress
, LPCSTR lpszDllName
, LPVOID pfnCurrent
, LPVOID pfnNew
)
{
ASSERT(hBaseAddress && lpszDllName && pfnCurrent && pfnNew );
// retrieve IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
DWORD dwSize = 0;
PIMAGE_SECTION_HEADER pFoundHeader = NULL;
PImgDelayDescr pImgDelayDescr
= (PImgDelayDescr)ImageDirectoryEntryToDataEx( hBaseAddress
, TRUE
, IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
, &dwSize
, &pFoundHeader
);
if( pImgDelayDescr == NULL ){ return FALSE; }
while (pImgDelayDescr->rvaDLLName)
{
if ( _strcmpi((CHAR*)((PBYTE)hBaseAddress+pImgDelayDescr->rvaDLLName),
lpszDllName) == 0 )
{
break;
}
++pImgDelayDescr;
}
// Not found in IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT
if( !pImgDelayDescr->rvaDLLName )
return FALSE;
// retrieve IAT
PIMAGE_THUNK_DATA pThunk = NULL;
if( (pImgDelayDescr->grAttrs & dlattrRva) == 0 )
return FALSE;
pThunk = (PIMAGE_THUNK_DATA)(((LPBYTE)hBaseAddress) + pImgDelayDescr->rvaIAT);
// enumerate functions in the IAT
while(pThunk->u1.Function)
{
PDWORD lpAddr = (PDWORD)&(pThunk->u1.Function);
if(*lpAddr == (DWORD)pfnCurrent)
{
// modify the address
::WriteProcessMemory(::GetCurrentProcess()
, lpAddr
, &pfnNew
, sizeof(DWORD)
, NULL
);
return TRUE;
}
pThunk++;
}
return FALSE;
}
Windows PE 加载器可能在进程启动后将额外的 DLL 加载到进程中,并且函数地址也可以通过 GetProcAddress
API 获取,因此有必要修改新加载的 DLL 中的 IAT。
通常,会挂钩 LoadLibary(Ex)
和 GetProcAddress
。但是,该示例挂钩 LdrLoadDll
和 LdrGetProcedureAddress
。LoadLibary(Ex)
和 GetProcAddress
的实现依赖于 ntdll.dll 中暴露的 LdrLoadDll
和 LdrGetProcedureAddress
,ntdll.dll 是从 Ring3 到 Ring0 的网关。
// Process those modules loaded dynamically,
NTSTATUS WINAPI CWininetHook::_LdrLoadDll
(IN PWCHAR PathToFile OPTIONAL, IN ULONG Flags OPTIONAL,
IN PUNICODE_STRING ModuleFileName, OUT PHANDLE ModuleHandle)
{
NTSTATUS ntStatus = s_pfnLdrLoadDll
( PathToFile, Flags, ModuleFileName, ModuleHandle);
if( ntStatus == STATUS_SUCCESS && (Flags & LOAD_LIBRARY_AS_DATAFILE) == 0 )
{
#ifdef _DEBUG
if( ModuleFileName->Length > 0 )
{
WCHAR wszPath[MAX_PATH] = {0};
memcpy_s( wszPath, MAX_PATH*sizeof(WCHAR),
ModuleFileName->Buffer, MAX_PATH*sizeof(WCHAR));
TRACE( L"LdrLoadDll %s\n", wszPath);
}
#endif
HANDLE hDll = *ModuleHandle;
ReplaceIATEntryInImageImportTable( hDll, "NTDLL.DLL",
s_pfnLdrLoadDll, &CWininetHook::_LdrLoadDll);
//......
}
return ntStatus;
}
// Return the user-provided function address instead of the read one
NTSTATUS WINAPI CWininetHook::_LdrGetProcedureAddress
(IN HMODULE ModuleHandle, IN PANSI_STRING FunctionName OPTIONAL,
IN WORD Oridinal OPTIONAL, OUT PVOID *FunctionAddress )
{
NTSTATUS ntStatus = s_pfnLdrGetProcedureAddress
( ModuleHandle, FunctionName, Oridinal, FunctionAddress);
if( ntStatus == STATUS_SUCCESS )
{
TCHAR tszPath[MAX_PATH] = {0};
if( GetModuleFileName( ModuleHandle, tszPath, MAX_PATH) )
{
CString strFile(tszPath);
int nFind = strFile.ReverseFind('\\');
if( nFind > 0 )
strFile = strFile.Mid(nFind+1);
if( strFile.CompareNoCase(_T("WININET.dll")) == 0 )
{
CHAR szFunName[1024] = {0};
memcpy( szFunName, FunctionName->Buffer, FunctionName->Length );
#ifdef _DEBUG
USES_CONVERSION;
TRACE( _T("LdrGetProcedureAddress %s\n"), A2T(szFunName));
#endif
TRY_GET_ADDRESS( "HttpSendRequestA",
s_pfnHttpSendRequestA, _HttpSendRequestA, PFN_HttpSendRequestA);
// ......
}
}
}
return ntStatus;
}
现在,我们可以注入到每个请求中,包括 AJAX 请求。如何在发送请求时获取/设置 HttpOnly Cookie?IE8 SDK 为 InternetGetCookieEx / InternetSetCookieEx 添加了一个新的标志 INTERNET_COOKIE_HTTPONLY
。
TCHAR tszTemp[10240] = {0};
DWORD dwSize = sizeof(tszTemp);
InternetGetCookieEx( _T("https://codeproject.org.cn/index.aspx")
, _T("ASP.NET_SessionId")
, tszTemp
, &dwSize
, INTERNET_COOKIE_HTTPONLY
, NULL
);
InternetSetCookieEx( strUrl
, _T("ASP.NET_SessionId")
, _T("XXXXXXXXX")
, INTERNET_COOKIE_HTTPONLY
, NULL
);
虽然 MSDN 声明“需要 Internet Explorer 8.0 或更高版本”,但它在我的 Internet Explorer 7.0 中仍然有效。
这应该只是与 WININET.dll 有关,与 mshtml.dll 无关。