COMMAND
getopt
SYSTEMS AFFECTED
SunOS versions 5.5.1, 5.5.1_x86, 5.5, 5.5_x86, 5.4, 5.4_x86, and
5.3.
PROBLEM
This text is L0pht copyright and by his author mudge. Text used
here is taken mostly from L0pht Advisory.
The getopt(3) function parses options from a program's command
list. Due to insufficient bounds checking by getopt(3) while
processing command line arguments, it is possible to overwrite
the internal stack space of programs that use getopt(3). This may
allow users to cause programs using getopt(3) to execute
arbitrary commands by supplying carefully crafted arguments to
these programs. If these programs are setuid or setgid, then
these commands may be run with those privileges.
Any dynamically or statically linked setuid or setgid program
that uses getopt(3) may be vulnerable.
A buffer overflow condition exists in the getopt(3) routine. By
supplying an invalid option and replacing argv[0] of a SUID
program that uses the getopt(3) function with the appropriate
address and machine code instructions, it is possible to
overwrite the saved stack frame and upon return(s) force the
processor to execute user supplied instructions with elevated
permissions.
While evaluating programs in the Solaris Operating System
environment it became apparent that changing many programs trust
argv[0] to never exceed a certain length. In addition it seemed
as though getopt was simply copying argv[0] into a fixed size
character array.
./test >>& ccc
Illegal instruction (core dumped)
Knowing that the code in ./test was overflow free it seemed that
the problem must exist in one of the functions dynamically linked
in at runtime through ld.so. A quick gander through the namelist
showed a very limited range of choices for the problem to exist
in.
00020890 B _end
0002088c B _environ
00010782 R _etext
U _exit
00010760 ? _fini
0001074c ? _init
00010778 R _lib_version
000105ac T _start
U atexit
0002088c W environ
U exit
0001067c t fini_dummy
0002087c d force_to_data
0002087c d force_to_data
000106e4 t gcc2_compiled.
00010620 t gcc2_compiled.
U getopt
00010740 t init_dummy
00010688 T main
Next we checked out getopt() - as it looked like the most likely
suspect.
#include <stdio.h>
main(int argc, char **argv)
{
int opt;
while ((opt = getopt(argc, argv, "a")) != EOF) {
switch (opt) {
}
}
}
>gcc -o test test.c
>./test -z
./test: illegal option -- z
Note the name it threw back at the beggining of the error message.
It was quite obvious that they are just yanking argv[0]. Changing
argv[0] in the test program confirms this.
for (i=0; i<4096; i++)
buffer[i] = 0x41;
argv[0] = buffer;
With the above in place we see the following result:
>./test -z
[lot's of A's removed]AAAAAAAAA: illegal option -- z
Bus error (core dumped)
By yanking out the object file from the static archive libc that
is supplied with Solaris our culprit was spotted [note - we
assumed that libc.a was built from the same code base that
libc.so was].
> nm getopt.o
U _dgettext
00000000 T _getopt
00000000 D _sp
U _write
00000000 W getopt
U optarg
U opterr
U optind
U optopt
U sprintf
U strchr
U strcmp
U strlen
Here we see one of the infamous non-bounds-checking routines:
sprintf(); More than likely the code inside getopt.c looks
something like the following:
getopt.c:
char opterr[SOMESIZE];
...
sprintf(opterr, argv[0]...);
Thus, whenever you pass in a non-existant option to a program
that uses getopt you run into the potential problem with trusting
that argv[0] is smaller than the space that has been allocated
for opterr[].
This is interesting on the Sparc architecture as getopt() is
usually called out of main() and you need two returns [note -
there are certain situations in code on Sparc architectures that
allow you to switch execution to your own code without needing
two returns. Take a look at the TBR for some enjoyable hacking]
due to the sliding register windows. Some quick analysis of SUID
programs on a standard Solaris 2.5 box show that most of these
programs exit() or more likely call some form of usage()-exit()
in the default case for getopt and thus are not exploitable.
However, at least two of these programs provide the necessary
returns to throw your address into the PC : passwd(1) login(1)
On Solaris X86 you do not need these double returns and thus a
whole world of SUID programs allow unpriveledged users to gain
root access: (list of programs vulnerable too big to put here.
sigh.)
Exploit:
$./exploit "/bin/passwd" 4375 2> foo
# id
uid=0(root) gid=1(other)
Note: the source code for the exploit will be made available on
the www.l0pht.com/advisories.html page in a couple of days.
As soon as this exploit will be exposed to public, I will putted
here (crv).
Special thanks go out to ][ceman for his co-work on this project
and mudge, the author.
SOLUTION
The vulnerability relating to getopt(3) is fixed by the following
patches:
OS version Patch ID
---------- --------
SunOS 5.5.1 103612-23
SunOS 5.5.1_x86 103613-23
SunOS 5.5 103187-25
SunOS 5.5_x86 103188-25
SunOS 5.4 101945-49
SunOS 5.4_x86 101946-43
SunOS 5.3 101318-87
Patches listed are available to all Sun customers via World Wide
Web at:
ftp://sunsolve1.sun.com/pub/patches/patches.html