Crontab file or cron drop-in directory written on Linux
Matches the file-modification leg of AN0805: an auditd PATH record naming one of the stores cron reads its schedule from — the per-user spool under /var/spool/cron/, the system-wide /etc/crontab and /etc/anacrontab, and the drop-in directories /etc/cron.d/ and the hourly, daily, weekly and monthly run-parts folders. The path list is MITRE's `CronFilePath` knob, populated here with the Debian and RHEL layouts; a distribution that keeps its spool elsewhere needs its own entry. ESXi is one of the technique's platforms and its crontab lives at the same /var/spool/cron/crontabs/root path, but ESXi runs no auditd, so this logsource does not reach it and the ESXi arm of the technique is uncovered. Matching is on `contains` and `endswith` rather than an absolute path because `crontab -e` writes a temporary file and renames it into the spool — the rename target is what shows up, not the editor's scratch file. That does not rescue the relative case: auditd records `name=` exactly as it was handed to the syscall, so `cd /etc/cron.d && echo '* * * * * root /tmp/x' > job` logs `name="job"`, with the directory on a separate CWD record that Sigma cannot join, and no path selection here can match it. Such a write is caught only by the fourth selection, which matches the audit `key` the watches below install and which rides on the SYSCALL record whatever form the path took — so it works only if those watches were loaded with that exact `-k cron` key. Two parts of the analytic cannot be expressed here. auditd emits PATH and SYSCALL as separate records for one event, and the writing process's `exe`, `uid` and `auid` live only on the SYSCALL side, so MITRE's `RunUser` knob ("only root may schedule jobs") cannot be applied inside this selection; and the analytic's second half — an uncommon binary later executing on the schedule, with MITRE's `ExecutionFrequency` knob thresholding how often — is a cross-event correlation Sigma has no way to state. The macOS arm AN0806 is not attempted, because its unifiedlog source has no standardised Sigma field names and inventing them would produce a rule that renders everywhere and matches nothing. Prerequisite: no distribution audits these paths by default, so this rule returns zero rows until watches such as `-w /var/spool/cron -p wa -k cron`, `-w /etc/crontab -p wa -k cron` and `-w /etc/cron.d -p wa -k cron` are loaded — and zero rows reads as quiet when it actually means blind. The `wa` permission also decides the noise floor: with it, only writes and attribute changes are recorded, whereas a watch that includes `r` makes every scan by the cron daemon itself match. UNVERIFIED AS AUTHORED — derived from MITRE ATT&CK DET0290, not hand-written and not tested by its author. Any lab result is on this rule's own page.Full descriptionShow less
The detection
The 3 SIEM queries below are previews produced by Siemphony’s own translator, not pySigma, and none has been executed against a real backend — review before you deploy.
detection.yml
title: Crontab file or cron drop-in directory written on Linuxid: d1211ae7-280f-4182-9b93-c55ba2d668bastatus: experimentaldescription: | Matches the file-modification leg of AN0805: an auditd PATH record naming one of the stores cron reads its schedule from — the per-user spool under /var/spool/cron/, the system-wide /etc/crontab and /etc/anacrontab, and the drop-in directories /etc/cron.d/ and the hourly, daily, weekly and monthly run-parts folders. The path list is MITRE's `CronFilePath` knob, populated here with the Debian and RHEL layouts; a distribution that keeps its spool elsewhere needs its own entry. ESXi is one of the technique's platforms and its crontab lives at the same /var/spool/cron/crontabs/root path, but ESXi runs no auditd, so this logsource does not reach it and the ESXi arm of the technique is uncovered. Matching is on `contains` and `endswith` rather than an absolute path because `crontab -e` writes a temporary file and renames it into the spool — the rename target is what shows up, not the editor's scratch file. That does not rescue the relative case: auditd records `name=` exactly as it was handed to the syscall, so `cd /etc/cron.d && echo '* * * * * root /tmp/x' > job` logs `name="job"`, with the directory on a separate CWD record that Sigma cannot join, and no path selection here can match it. Such a write is caught only by the fourth selection, which matches the audit `key` the watches below install and which rides on the SYSCALL record whatever form the path took — so it works only if those watches were loaded with that exact `-k cron` key. Two parts of the analytic cannot be expressed here. auditd emits PATH and SYSCALL as separate records for one event, and the writing process's `exe`, `uid` and `auid` live only on the SYSCALL side, so MITRE's `RunUser` knob ("only root may schedule jobs") cannot be applied inside this selection; and the analytic's second half — an uncommon binary later executing on the schedule, with MITRE's `ExecutionFrequency` knob thresholding how often — is a cross-event correlation Sigma has no way to state. The macOS arm AN0806 is not attempted, because its unifiedlog source has no standardised Sigma field names and inventing them would produce a rule that renders everywhere and matches nothing. Prerequisite: no distribution audits these paths by default, so this rule returns zero rows until watches such as `-w /var/spool/cron -p wa -k cron`, `-w /etc/crontab -p wa -k cron` and `-w /etc/cron.d -p wa -k cron` are loaded — and zero rows reads as quiet when it actually means blind. The `wa` permission also decides the noise floor: with it, only writes and attribute changes are recorded, whereas a watch that includes `r` makes every scan by the cron daemon itself match. UNVERIFIED AS AUTHORED — derived from MITRE ATT&CK DET0290, not hand-written and not tested by its author. Any lab result is on this rule's own page.references: - https://attack.mitre.org/techniques/T1053/003 - https://attack.mitre.org/detectionstrategies/DET0290author: Siemphony pipeline (machine-derived from MITRE ATT&CK v19.2)date: 2026-08-16tags: - attack.execution - attack.persistence - attack.privilege-escalation - attack.t1053.003logsource: product: linux service: auditddetection: selection_spool: type: 'PATH' name|contains: - '/var/spool/cron/' selection_dropin_dirs: type: 'PATH' name|contains: - '/etc/cron.d/' - '/etc/cron.hourly/' - '/etc/cron.daily/' - '/etc/cron.weekly/' - '/etc/cron.monthly/' selection_system_tables: type: 'PATH' name|endswith: - '/etc/crontab' - '/etc/anacrontab' selection_watch_key: key: 'cron' condition: 1 of selection*falsepositives: - "Package installation, upgrade and removal. Distribution packages routinely ship their own schedule into /etc/cron.d or /etc/cron.daily — logrotate, mlocate/plocate, man-db, sysstat, certbot and unattended-upgrades among them — so every apt or dnf transaction on every host writes several matching PATH records. On a patched estate this is the highest-volume match by a wide margin and is why this rule is low rather than medium." - "Configuration management convergence. The Ansible cron module, Puppet cron resources, Chef and Salt all rewrite /var/spool/cron/crontabs/<user> or drop a fragment into /etc/cron.d on every run, which on a half-hourly schedule means a match per managed host per run whether or not the content changed." - "Agent and appliance installers. Backup, monitoring, vulnerability-scanning and antivirus packages install their own cron entry at setup and rewrite it at each version upgrade, under root, from a legitimate package manager." - "A user or administrator running `crontab -e` to add, edit or remove a normal job, or reinstalling a saved table with `crontab mytab`. This is the everyday supported way to schedule work and is separated from the technique only by who did it and what the entry runs." - "Host provisioning: cloud-init runcmd blocks, container image builds and golden-image bake scripts writing crontabs while the machine is being created, which produces a burst of matches on every new host."level: lowSplunk · SPL
Run this as a search.
index=* ((type="PATH" AND name="*/var/spool/cron/*") OR (type="PATH" AND (name="*/etc/cron.d/*" OR name="*/etc/cron.hourly/*" OR name="*/etc/cron.daily/*" OR name="*/etc/cron.weekly/*" OR name="*/etc/cron.monthly/*")) OR (type="PATH" AND (name="*/etc/crontab" OR name="*/etc/anacrontab")) OR key="cron")Elastic · ES|QL
Run this as a search.
FROM logs-*| WHERE ((TO_LOWER(type) == "path" AND TO_LOWER(name) LIKE "*/var/spool/cron/*") OR (TO_LOWER(type) == "path" AND (TO_LOWER(name) LIKE "*/etc/cron.d/*" OR TO_LOWER(name) LIKE "*/etc/cron.hourly/*" OR TO_LOWER(name) LIKE "*/etc/cron.daily/*" OR TO_LOWER(name) LIKE "*/etc/cron.weekly/*" OR TO_LOWER(name) LIKE "*/etc/cron.monthly/*")) OR (TO_LOWER(type) == "path" AND (TO_LOWER(name) LIKE "*/etc/crontab" OR TO_LOWER(name) LIKE "*/etc/anacrontab")) OR TO_LOWER(key) == "cron")
Wazuh · XML rule
Deploy to your manager — this is a rule, not a search.
<group name="sigma,linux,"> <!-- Rule ids must be unique on your manager; 100000+ is the user range. --> <!-- 4 rules: the Sigma condition ORs across different fields, which one rule cannot express. Any one matching is a hit. --> <rule id="100000" level="5"> <!-- Set <if_sid> to the decoder/base rule for linux so this only evaluates relevant events. --> <field name="type" type="pcre2">(?i)^PATH$</field> <field name="name" type="pcre2">(?i)/var/spool/cron/</field> <description>Crontab file or cron drop-in directory written on Linux (1/4)</description> <mitre> <id>T1053.003</id> </mitre> </rule> <rule id="100001" level="5"> <!-- Set <if_sid> to the decoder/base rule for linux so this only evaluates relevant events. --> <field name="type" type="pcre2">(?i)^PATH$</field> <field name="name" type="pcre2">(?i)(/etc/cron\.d/|/etc/cron\.hourly/|/etc/cron\.daily/|/etc/cron\.weekly/|/etc/cron\.monthly/)</field> <description>Crontab file or cron drop-in directory written on Linux (2/4)</description> <mitre> <id>T1053.003</id> </mitre> </rule> <rule id="100002" level="5"> <!-- Set <if_sid> to the decoder/base rule for linux so this only evaluates relevant events. --> <field name="type" type="pcre2">(?i)^PATH$</field> <field name="name" type="pcre2">(?i)(/etc/crontab$|/etc/anacrontab$)</field> <description>Crontab file or cron drop-in directory written on Linux (3/4)</description> <mitre> <id>T1053.003</id> </mitre> </rule> <rule id="100003" level="5"> <!-- Set <if_sid> to the decoder/base rule for linux so this only evaluates relevant events. --> <field name="key" type="pcre2">(?i)^cron$</field> <description>Crontab file or cron drop-in directory written on Linux (4/4)</description> <mitre> <id>T1053.003</id> </mitre> </rule></group>
Sentinel · KQL
Run this as a search.
Sentinel cannot express this construct. No Sentinel table is modelled for this logsource ({"product":"linux","service":"auditd"}), so there is nothing honest to query. Pick a logsource this translator maps, or write the KQL by hand. The Sigma source is on the first tab, unchanged.
Verdicts · reactions · comments
Community
Verdicts from engineers who actually deployed it, and the conversation around it.
Nobody has run this in a real environment and said what happened.
Verdicts from engineers who deployed it
0 castNo verdicts reported yet. The first one is worth more than the tenth — say what you ran it against.
Log in to report a verdict.
Log in to join the discussion.
No comments yet. Someone who deploys this will have something to say about it.