Skip to content
HackIndex logo

HackIndex

Cron Jobs and Scheduled Tasks Enumeration on Linux

4 min read Apr 24, 2026

Scheduled tasks running as root are one of the most reliable privilege escalation sources on Linux. This page covers locating and reading all scheduled tasks — cron jobs, systemd timers, and at jobs. Exploiting what you find here is covered separately in Cron Job Privilege Escalation and Systemd Unit and Timer Privilege Escalation.

System-Wide Crontab

user@host ~ $ cat /etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

# m  h  dom  mon  dow  user   command
17 *    * * *   root    cd / && run-parts --report /etc/cron.hourly
25 6    * * *   root    test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
*  *    * * *   root    /opt/scripts/backup.sh

The user column shows who the command runs as. Any entry running as root with a writable script or binary path is a privilege escalation lead.

Cron Drop-In Directories

┌──(kali㉿kali)-[~]
└─$ ls -la /etc/cron.d/
┌──(kali㉿kali)-[~]
└─$ ls -la /etc/cron.daily/
┌──(kali㉿kali)-[~]
└─$ ls -la /etc/cron.hourly/
┌──(kali㉿kali)-[~]
└─$ ls -la /etc/cron.weekly/
┌──(kali㉿kali)-[~]
└─$ ls -la /etc/cron.monthly/

Read each file in these directories:

user@host ~ $ cat /etc/cron.d/*

/etc/cron.d/ files follow the same format as /etc/crontab with an explicit user field. The run-parts directories contain scripts that execute as root on a schedule — check permissions on both the directory and each script inside.

Per-User Crontabs

user@host ~ $ crontab -l
user@host ~ $ crontab -l -u root 2>/dev/null

crontab -l shows the current user's crontab. Reading root's crontab directly requires root — but it is worth trying in case of misconfiguration.

user@host ~ $ ls -la /var/spool/cron/crontabs/ 2>/dev/null
user@host ~ $ cat /var/spool/cron/crontabs/* 2>/dev/null

On Debian-based systems, user crontabs are stored here. If the directory or files are readable, you can see scheduled tasks for all users including root.

Anacron

user@host ~ $ cat /etc/anacrontab 2>/dev/null

Anacron handles jobs on systems that are not always on — it runs missed jobs on next boot. The format includes delay and job ID fields alongside the command. Jobs here run as root unless specified otherwise.

Checking Script Permissions

Once you have identified what scripts cron is calling, check whether you can write to them:

user@host ~ $ ls -la /opt/scripts/backup.sh
user@host ~ $ ls -la /opt/scripts/

A script writable by your current user that runs as root is an immediate escalation path. Also check the directory — a writable directory allows replacing the script entirely even if the file itself is not writable.

Check all scripts referenced in cron entries at once:

user@host ~ $ grep -r '/' /etc/crontab /etc/cron.d/ /etc/anacrontab 2>/dev/null | grep -oP '(/[a-zA-Z0-9._/-]+)' | sort -u | xargs ls -la 2>/dev/null

Wildcard Usage in Cron Commands

Look for cron commands using wildcards with tools like tar, rsync, chown, or find:

user@host ~ $ grep -r '\*' /etc/crontab /etc/cron.d/ 2>/dev/null

Wildcards in these commands can be abused through filename injection. This is a separate technique — if you find it, the exploitation is covered in the Wildcard Injection privilege escalation page.

Systemd Timers

user@host ~ $ systemctl list-timers --all
NEXT                          LEFT          LAST                          PASSED       ACTIVATES
Sun 2024-04-28 04:00:00 UTC   3h 14min left Sat 2024-04-27 04:00:00 UTC  20h ago      fstrim.timer
Sun 2024-04-28 06:00:00 UTC   5h 14min left Sat 2024-04-27 06:20:01 UTC  17h ago      apt-daily-upgrade.timer
*:0/5                         4min 32s left Sun 2024-04-28 00:45:01 UTC  7s ago       deploy.timer

The ACTIVATES column shows the service triggered by each timer. Check the deploy.timer service unit and its ExecStart binary for writable paths.

user@host ~ $ systemctl cat deploy.timer
user@host ~ $ systemctl cat deploy.service

at Jobs

user@host ~ $ atq
user@host ~ $ at -l

atq lists pending one-time jobs scheduled with at. These are less common than cron but worth checking. Reading the job content requires the job to belong to your user or root access:

user@host ~ $ at -c <job_number>

Monitoring for Cron Execution

To catch cron jobs that run infrequently and might not appear in static enumeration, monitor process creation:

user@host ~ $ watch -n 1 'ps aux --sort=start_time | tail -20'

Or transfer pspy to the target to monitor /proc without root:

user@host ~ $ ./pspy64

Let it run for a few minutes around the top of the hour when most cron jobs execute. Short-lived root processes that appear on a schedule confirm the cron entry and show the full command being run.