You’ve been compromised. The bad news: an attacker has shell access to your web server. The worse news: they’re running as www-data. The really bad news? They’re about to become root.
Here’s the thing about Linux privilege escalation: it’s not magic. It’s a methodology. An attacker gets a foothold at a low privilege level and methodically looks for ways to climb. They’ll poke at SUID binaries, check what you can run with sudo, hunt for world-writable files in system paths. Recent exploits like OpenClaw remind us that this is real—and it’s happening right now.
The good news? You can stop them. Not with firewalls. Not with IDS. With boring, unglamorous hardening. Let me walk you through the ladder they’re climbing and how to kick it down.
The Common Attack Vectors
SUID/SGID Binaries: The Trojan Horse
SUID (Set User ID) binaries are programs that run as their owner, regardless of who executes them. This is useful—sudo needs it, for instance. But if an attacker finds an SUID binary with a vulnerability, they just won with six characters of code.
First, find what you’ve got:
find / -perm -4000 -type f 2>/dev/nullThat -4000 is the SUID bit. You’ll probably see sudo, mount, ping, maybe passwd. The question is: do you need all of them? A web server doesn’t need mount. A containerized app doesn’t need ping.
Defense: Audit your SUID binaries quarterly. Remove any that aren’t actively used. If you must keep them, restrict their execution with SELinux or AppArmor policies.
Sudo Misconfigurations: The Golden Ticket
The first thing an attacker does when they compromise a system is run sudo -l to see what they can execute as root without a password. If you’ve configured sudo carelessly—like allowing NOPASSWD: ALL for a service account—they’ve won.
Here’s what not to do:
www-data ALL=(ALL) NOPASSWD: /usr/bin/systemctlappuser ALL=(ALL) NOPASSWD: /bin/bashThis is signing your server over to the first attacker who compromises that user. If they get into www-data, one command and they’re root.
Defense: Audit /etc/sudoers with sudo visudo. Only grant specific commands (not wildcards). Require passwords for anything dangerous. Use sudo -l regularly on each service account to see what’s actually exposed.
Cron Jobs: The Root-Owned Time Bomb
If root runs a cron job that executes or sources a script you can write to, you’ve just become root. This happens more than you’d think—especially in hastily-assembled automation.
#!/bin/bashrm -rf /tmp/cache/*# ^ if /tmp/cache is world-writable, we have a problemAn attacker can replace /tmp/cache/malicious with executable code and wait for root’s cron job to run it.
Defense: If root must run a script, make sure all parent directories and the script itself are owned by root and writable only by root. Use find to check:
find /tmp -writable -not -user root 2>/dev/nullBetter yet: use systemd timers instead of cron. They’re easier to audit and less prone to path-traversal issues.
Kernel Exploits: The Nuclear Option
A vulnerable kernel is a privilege escalation goldmine. Attackers use tools like linpeas.sh to check your kernel version against known CVEs. If you’re running something from 2022 with a known root-escape exploit, you’re getting rooted.
Defense: Keep your kernel patched. Run uname -r weekly. On Ubuntu, apt update && apt upgrade. On CentOS, yum update. Yes, it requires a reboot sometimes. Live with it.
Additionally, use seccomp profiles and gVisor (if you’re running containers) to limit the syscalls an attacker can make, even if they exploit the kernel.
Docker Socket Exposure: The Instant Root
If a low-privilege container user can reach /var/run/docker.sock, they can spawn a privileged container and mount the host’s filesystem. They’re root.
# Bad: www-data user can access docker socketls -la /var/run/docker.sock# If www-data can read/write it, game overDefense: The docker socket should only be accessible by root and the docker group. Remove untrusted users from the docker group. Better yet: use rootless Docker.
Capabilities Abuse: The Sledgehammer Approach
Linux capabilities let you grant specific privileges without full root access. But if you grant cap_sys_admin to a service, you’ve given them the keys to most of the kernel.
# Check capabilities on a binarygetcap /usr/bin/some_toolDefense: Use setcap sparingly. Prefer the bare minimum capability needed. For example, ping only needs cap_net_raw, not cap_sys_admin. Review capabilities annually.
World-Writable Files: The Obvious Win
If /usr/local/bin/important_script.sh is writable by anyone, an attacker will replace it.
find / -writable -type f -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null | head -20Defense: Run this command monthly. Remove write permissions from files in system paths:
chmod o-w /usr/local/bin/important_script.shchmod g-w /usr/local/bin/important_script.shWeak Service Accounts: Unnecessary Root Privileges
Some services run as root for historical reasons. Nginx, Postgres, and most application servers can run as unprivileged users and use capabilities for the few operations they need.
Defense: Audit your running services:
ps aux | grep -E "root.*nginx|root.*postgres|root.*java"If you see unnecessary root services, reconfigure them to use dedicated unprivileged accounts.
Tools for Offense (and Defense)
Run these yourself to find what an attacker would find:
linpeas.sh — A comprehensive privilege escalation scanner that checks all of the above:
curl -L https://github.com/carlospolop/PEASS-ng/releases/latest/download/linpeas.sh | bashlinenum.sh — Another solid scanner, simpler than linpeas:
curl -L https://raw.githubusercontent.com/rebootuser/LinEnum/master/LinEnum.sh | bashRun these on your own infrastructure as part of your quarterly hardening review.
The Quick Hardening Audit
Save this script and run it monthly:
#!/bin/bashset -e
echo "=== SUID Binaries ==="find / -perm -4000 -type f 2>/dev/null | wc -lecho "Above 5? Consider auditing."
echo ""echo "=== Sudo Entries (Potentially Dangerous) ==="sudo grep -E "NOPASSWD|ALL=\(ALL\)" /etc/sudoers 2>/dev/null || echo "None found (good)."
echo ""echo "=== World-Writable Files (First 10) ==="find / -writable -type f -not -path "/proc/*" -not -path "/sys/*" -not -path "/tmp/*" 2>/dev/null | head -10
echo ""echo "=== Running Services As Root ==="ps aux | grep "^root" | grep -v "kthread" | wc -lecho "More than 10? Investigate which can be deprivileged."
echo ""echo "=== Kernel Version ==="uname -r
echo ""echo "=== Docker Socket Permissions ==="ls -la /var/run/docker.sock 2>/dev/null || echo "Docker not running (good)."The Defensive Checklist
- Run
sudo visudoand audit every entry. Remove NOPASSWD and wildcards. - Find and audit SUID binaries with
find / -perm -4000. Remove unnecessary ones. - Check cron jobs in
/var/spool/cron/and/etc/cron.d/. Ensure they don’t source world-writable files. - Run
uname -rand verify your kernel is within 6 months of the latest stable release. - Run linpeas.sh and address the red/yellow flags.
- Check
/var/run/docker.sockpermissions. Remove untrusted users from the docker group. - Audit service accounts. Deprivilege anything running as root unnecessarily.
- Remove write permissions from files in
/usr/bin,/usr/local/bin,/etc. - Enable SELinux or AppArmor if your distro supports it.
- Set up quarterly audits with the script above.
The Real Talk
Privilege escalation isn’t a single vulnerability. It’s an attacker methodology. They’ll try ten vectors before one works. Your job is to close as many as possible. You won’t get them all—but you’ll make their life expensive enough that they move to softer targets.
The OpenClaw CVE is fresh. The next one is coming. The difference between “completely owned” and “an attacker got stuck at low privilege” is the work you do right now, when everything’s running fine.
Go run that audit script. You’ll probably find something.