Repository navigation
Add Linux Auditd Audit Rules Removed detection (T1562.001) - #4386
vahidmalekk wants to merge 2 commits into
Conversation
Defines the CONFIG_CHANGE auditd data source (audit rule add/remove at runtime) used by the Linux Auditd Audit Rules Removed detection.
Detects a runtime flush of audit rules (CONFIG_CHANGE op=remove_rule with no matching add_rule in the window) - auditctl -D / AUDIT_DEL - which blinds auditing while the daemon keeps running (no DAEMON_END). Legit reloads re-add rules and are excluded by rules_added=0. T1562.001 / T1070.
|
@codex review |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f351e10757
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| search: |- | ||
| `linux_auditd` type=CONFIG_CHANGE op IN ("remove_rule", "add_rule") | ||
| | bin _time span=1m | ||
| | stats count(eval(op=="remove_rule")) as rules_removed count(eval(op=="add_rule")) as rules_added values(key) as removed_keys min(_time) as firstTime max(_time) as lastTime BY _time host auid ses subj |
There was a problem hiding this comment.
Preserve removals when
subj is absent
CONFIG_CHANGE records can omit subj on hosts without an active LSM security context, and the new data source does not require that field. Splunk's stats ... BY ... subj drops events with no value for that grouping field, so a qualifying auditctl -D on such a host produces no row and no detection; coalesce the optional context before stats or do not group on it.
Useful? React with 👍 / 👎.
| `linux_auditd` type=CONFIG_CHANGE op IN ("remove_rule", "add_rule") | ||
| | bin _time span=1m | ||
| | stats count(eval(op=="remove_rule")) as rules_removed count(eval(op=="add_rule")) as rules_added values(key) as removed_keys min(_time) as firstTime max(_time) as lastTime BY _time host auid ses subj | ||
| | where rules_removed>=5 AND rules_added=0 |
There was a problem hiding this comment.
Require re-additions to correspond to removed rules
If an attacker flushes the rules and then adds one arbitrary harmless rule in the same minute and session, rules_added becomes 1 and this predicate discards the group even if hundreds of removed rules remain absent. Because a legitimate reload is expected to restore the removed rules, treating any single add as a reload gives the attacker targeted by this detection a trivial bypass; compare the re-added rules or counts with the removals instead.
Useful? React with 👍 / 👎.
| - Linux Auditd Config Change | ||
| search: |- | ||
| `linux_auditd` type=CONFIG_CHANGE op IN ("remove_rule", "add_rule") | ||
| | bin _time span=1m |
There was a problem hiding this comment.
Correlate reloads across minute boundaries
When a legitimate reload starts near a wall-clock minute boundary, this fixed bin places its removal records in the preceding bucket and its add records in the following bucket. The first bucket can therefore satisfy rules_removed>=5 AND rules_added=0 and raise the high-severity result even though the rules were immediately restored, contradicting the documented reload exclusion; correlate over a sliding reload window rather than an aligned one-minute bucket.
Useful? React with 👍 / 👎.
| search: |- | ||
| `linux_auditd` type=CONFIG_CHANGE op IN ("remove_rule", "add_rule") | ||
| | bin _time span=1m | ||
| | stats count(eval(op=="remove_rule")) as rules_removed count(eval(op=="add_rule")) as rules_added values(key) as removed_keys min(_time) as firstTime max(_time) as lastTime BY _time host auid ses subj |
There was a problem hiding this comment.
Exclude failed rule-removal operations
CONFIG_CHANGE can emit op=remove_rule for rejected deletion requests with res=no/res=0, but this count ignores res. Five failed requests in one minute therefore meet the threshold and generate a risk event claiming rules were removed even though the ruleset is unchanged; count only successful removals, or report failed attempts separately.
Useful? React with 👍 / 👎.
Summary
Adds a new anomaly analytic, Linux Auditd Audit Rules Removed, plus a new Linux Auditd Config Change data source, detecting a runtime flush of the audit ruleset (T1562.001 / T1070) via the native
CONFIG_CHANGE op=remove_ruleevents.Coverage gap
ESCU has auditd daemon/service-stop detections, but an attacker who runs
auditctl -D(or anAUDIT_DELnetlink) flushes the ruleset while the daemon keeps running - noDAEMON_ENDfires, so every existing auditd rule stays silent even though host auditing is now blind. This closes that gap. It complements the companion PR Linux Auditd Audit Configuration File Modification (on-disk config) - this one covers the runtime flush.How it works / FP handling
The kernel emits a
CONFIG_CHANGErecord per rule add/removal (no custom audit rule needed). Legitimate reloads -augenrules --load,auditctl -R, service restart, boot - remove then re-add the rules in the same window, so the search fires only whenrules_removed >= 5andrules_added = 0(a flush, not a reload). Validated live: a real reload removed 1856 / added 2056 in one minute and was correctly suppressed.New data source
data_sources/linux_auditd_config_change.yml(separatortype=CONFIG_CHANGE) - there was no existing CONFIG_CHANGE auditd data source. Happy to adjust its fields/naming to STRT conventions.Testing
True-positive dataset captured on an authorized lab host: eight
op=remove_ruleevents with no matchingadd_rule. Paired attack data: splunk/attack_data#1261;tests.attack_datapoints at it, so the unit test passes once #1261 merges.MITRE ATT&CK
T1562.001 (Impair Defenses: Disable or Modify Tools), T1070 (Indicator Removal)
The
rules_removed>=5threshold (flush vs single-rule tuning) and the system-context exclusion are documented as tunable in how_to_implement / the filter macro.