notes 2026-06-26 · 5 min read

Linux Privilege Escalation Beyond sudo -l

Linux privilege escalation past sudo -l: SUID binaries, capabilities, cron jobs, systemd units, group membership and the Docker socket.

Linux Privilege Escalation Beyond sudo -l

Running sudo -l is a good start. Stopping there makes you miss half the machine.

Linux privilege escalation usually appears in the gap between what a process should be able to do and what it can actually do.

That gap may come from sudo, SUID or capabilities. Cron, systemd and groups create other paths. Sockets, credentials, mounts and file permissions matter too. So do services running in a stronger security context.

The examples below assume a lab or an authorized penetration test.

Common Linux privilege escalation surfaces

Start with context

Before looking for an exploit, I answer four questions.

Who am I?

Which groups am I in?

Which kernel and distribution are running?

Which interesting paths can I read or modify?

Basic commands already reveal a lot:

1
2
3
4
id
whoami
uname -a
cat /etc/os-release

Then I look at processes:

1
ps auxww

Sockets:

1
ss -lntup

Mounts:

1
findmnt

And readable files in sensitive locations:

1
2
find /etc /opt /srv /var/www -maxdepth 3 \
  -type f -readable 2>/dev/null

The goal is to build context before launching automated enumeration.

sudo -l is still mandatory

Run:

1
sudo -l

Look for allowed commands, argument restrictions, and preserved environment variables.

An innocent-looking binary may support a shell escape.

An allowed script may import a writable file.

A wildcard may interpret attacker-controlled input as an argument.

NOPASSWD lowers the barrier significantly.

GTFOBins helps recall useful behavior in common binaries, but the actual sudo rule still determines whether the path works.

SUID deserves careful reading

A SUID file runs with the effective UID of its owner.

When the owner is root, that can expose privileged operations to ordinary users.

Enumeration:

1
find / -perm -4000 -type f 2>/dev/null

The presence of SUID alone does not prove a vulnerability.

passwd uses SUID legitimately.

The problem appears when a SUID program executes commands unsafely, trusts a controllable PATH, loads libraries dangerously, or exposes functionality that can escape into a shell.

Capabilities break the simple root model

Since Linux 2.2, privileges traditionally concentrated in root can be split into capabilities.

Enumeration:

1
getcap -r / 2>/dev/null

Some capabilities deserve immediate attention.

CAP_SETUID may allow UID changes.

CAP_DAC_OVERRIDE can bypass file permission checks.

CAP_SYS_ADMIN is extremely broad.

CAP_SYS_PTRACE can expose other processes.

Risk depends on the binary receiving the capability.

Python with cap_setuid+ep creates a very different scenario from a small purpose-built utility.

Lab example:

1
/usr/bin/python3 -c 'import os; os.setuid(0); os.system("/bin/sh")'

That only works when the process has enough capability for the operation.

Capabilities change privilege without SUID

Cron turns writable files into future execution

Scheduled tasks running as root deserve inspection.

Check:

1
2
cat /etc/crontab
ls -la /etc/cron.d /etc/cron.hourly /etc/cron.daily

Then follow the scripts.

If root runs:

1
/opt/backup/run.sh

and your user can edit that file, the path is almost complete.

Inspect directories used by the script too.

The main file may be root-only while an imported module, loaded config, or PATH-resolved command remains writable.

systemd creates similar paths

List interesting services:

1
systemctl list-units --type=service --all

Inspect the unit:

1
systemctl cat backup.service

Look at ExecStart, EnvironmentFile, helper scripts, and writable directories.

A root service executing a script under /opt/tools/ becomes risky if that script or one of its dependencies can be changed.

Another common case is a user allowed to start or restart one specific service through sudo.

If that user controls the executable started by the service, the restart becomes the trigger.

Groups matter more than they look

id shows group membership.

Some groups deserve immediate attention.

docker can be equivalent to root access in many setups because the daemon usually runs as root.

lxd can also expose strong paths.

Backup, disk, or libvirt groups may provide access that crosses the normal user boundary.

Do not memorize “group X always means root.”

Look at the resource that group controls on this machine.

The Docker socket is a major trust boundary

Check:

1
ls -l /var/run/docker.sock

If your user can talk to the Docker daemon, the next question is what that daemon allows you to create.

In a lab, a container with the host filesystem mounted can demonstrate the impact:

1
2
3
docker run --rm -it \
  -v /:/host \
  alpine chroot /host /bin/sh

The exact result depends on daemon and policy configuration.

The core issue is control over a privileged service.

Forgotten credentials solve many machines

Privilege escalation does not always need a local vulnerability.

Look at application configs, scripts, history files, and backups.

Useful locations often include:

1
2
3
4
5
6
/var/www/
/opt/
/srv/
/home/*/.config/
/home/*/.ssh/
/etc/systemd/system/

A local database may hold a reused password.

An .env file may expose another user’s credential.

A backup script may contain a root password in clear text.

A pentester who only searches for SUID misses these routes.

A writable path needs a consumer

Finding a writable file is only the beginning.

Ask who executes it.

Ask when it runs.

Ask which UID runs it.

Ask which input it consumes.

Ask which modules it imports.

Ask which working directory it assumes.

The attack path appears when a low-privileged user controls something that a high-privileged process later interprets.

Kernel exploits belong near the end

An old kernel deserves investigation.

I do not start there.

Kernel exploitation can crash the host, corrupt state, and make the test more invasive.

In a pentest, I exhaust configuration, credential, and trust-boundary issues first.

If the kernel remains the required route, I validate exact version, vendor patch status, and preconditions before execution.

Automation helps, but read the result

LinPEAS, LinEnum, and similar tools accelerate collection.

They also produce a lot of noise.

I use automation to find clues.

Then I return to the host and validate each relationship manually.

A convincing path should fit into a short chain:

1
2
3
4
5
6
7
8
9
10
low-privileged user
   |
   v
controllable file or resource
   |
   v
privileged process consumes it
   |
   v
execution as root

How to reason about privilege escalation

The pattern that keeps repeating

Most local privilege escalation can be described as control over input that crosses into a privileged context.

That input may be a sudo argument.

It may be a cron script.

It may be a capability.

It may be a socket.

It may be a unit file.

It may be a credential.

Once you start looking for that relationship, sudo -l remains useful. It simply stops being the whole map.

References

Linux man-pages, capabilities(7).

GTFOBins, GTFOBins.

systemd, systemd.service manual.

This post is licensed under CC BY 4.0 by the author.