Dev.to Security 🔐 Cybersecurity 👁 0 📖 5 min read

Three reasons a custom Wazuh rule stays silent after a clean config check, measured on 4.14.7

A custom Wazuh rule that never fires rarely has a broken regex. In our lab the usual story is quieter: wazuh-analysisd -t exits 0, the manager restarts cleanly, and the rule still never produces an alert. We measured th

A custom Wazuh rule that never fires rarely has a broken regex. In our lab the usual story is quieter: wazuh-analysisd -t exits 0, the manager restarts cleanly, and the rule still never produces an alert.

We measured three ways this happens on Wazuh 4.14.7. None of them is visible from the exit code of the config check.

1. Wazuh dropped the rule while loading it

If a rule's if_sid parent does not exist at the moment the rule is read, Wazuh ignores the rule, prints two warnings, and carries on. We wrote up the file-name case in an earlier post: a child of 5715 in 0094-test.xml is dropped, the same rule in 0096-test.xml fires.

This week someone running Wazuh in production described two cases that happen inside a single file, so we measured those too:

<group name="test,">
  <!-- case A: the parent is defined later in the same file -->
  <rule id="999910" level="8">
    <if_sid>999911</if_sid>
    <match>RDTEST_FWD_CHILD</match>
    <description>child, written before its parent</description>
  </rule>
  <rule id="999911" level="3">
    <match>RDTEST_FWD</match>
    <description>parent</description>
  </rule>

  <!-- case B: if_sid points to the rule itself -->
  <rule id="999920" level="8">
    <if_sid>999920</if_sid>
    <match>RDTEST_SELF</match>
    <description>self-reference</description>
  </rule>
</group>
case -t exit warnings renamed to 9999_...xml logtest and live events
A: parent later in the same file 0 7617 + 7619 for 999910 same result only the parent 999911 matched
B: if_sid points to itself 0 7617 + 7619 for 999920 same result the rule never fired

The warnings for case A, word for word:

WARNING: (7617): Signature ID '999911' was not found and will be ignored in the 'if_sid' option of rule '999910'.
WARNING: (7619): Empty 'if_sid' value. Rule '999910' will be ignored.

The rule count agrees. Our test files held 7 rules in total: the three above and four others that load normally. With them in place the manager logged Total rules enabled: '8468'; after we removed them, '8463'. That is 5 more, not 7: the 2 missing are 999910 and 999920.

Renaming the file so it loads last does not help here, because the problem is not the order between files. Wazuh reads a file top to bottom, so a parent must come before its children in the same file, and a rule cannot be its own parent.

2. A sibling rule takes the event

Rules that hang off the same parent are tried in order of level, highest first. "Hang off" includes more than if_sid: the stock rule 40112 (level 12, failures followed by a success) is attached to 5715 through <if_group>authentication_success</if_group>, a group that 5715 carries. We put one child of the stock sshd rule 5715 in local_rules.xml (<if_sid>5715</if_sid>, <match>Accepted password</match>) and sent two sequences through wazuh-logtest:

  • S1: one Accepted password line on its own
  • S2: eight Failed password lines from the same IP within eight seconds, then the same Accepted password line
our rule -t S1 lands on S2 lands on
level 9 exit 0, no warnings ours 40112 (level 12), ours loses
level 13 exit 0, no warnings ours ours

The rule and the parent are the same in both rows. Only the level changed, and that decided who got the event after a burst of failures. -t said nothing in either case, because nothing is wrong with the rule. It loaded, and a sibling won.

3. It passes logtest because logtest saw one line

One line through wazuh-logtest tests a decoder and a match. It does not test what happened before that line. We measured three things a one-line test cannot show.

Counting. A composite rule with <if_matched_sid> and frequency="5" timeframe="60", fed twelve matching events in one logtest session, fired on the 5th and the 10th. The 6th did not fire again: after an alert the count starts from zero. With <same_source_ip/>, events from two IPs alternating fired on the 9th and 10th event, the 5th from each IP. Each IP is counted on its own.

Restarts. Rule changes are usually applied with a manager restart, and a restart resets these counts. With frequency="4" on real log lines read by logcollector: 2 failures, restart, 2 more failures gave no alert. All 4 events reached the manager; the count simply began again after the restart.

Events that never arrive. Logtest only sees what you paste into it. On a real manager, events can be lost before analysis. We flooded one agent with 900,000 lines with its client buffer turned off. 57,047 arrived, and about 94% were lost at the agent, with one warning in the agent's own ossec.log as the only trace. Full write-up.

Check yours

Rules dropped at load, before a restart (read the output, not the exit code):

/var/ossec/bin/wazuh-analysisd -t 2>&1 | grep -E '\((7617|7619)\)'

After a restart, the same warnings are in the manager log:

grep -E '\((7617|7619)\)' /var/ossec/logs/ossec.log

Every rule id in a 7619 line is a rule that is not running.

Siblings that might take your event. Look up the parent's groups, then list the rules attached to the parent by id or by one of those groups, and compare levels:

grep -rn -A8 'id="5715"' /var/ossec/ruleset/rules | grep '<group>'
grep -rnE '<if_sid>([^<]*[ ,])?5715([ ,][^<]*)?</if_sid>|<if_group>authentication_success</if_group>' \
  /var/ossec/ruleset/rules /var/ossec/etc/rules

Replace 5715 and authentication_success with your rule's parent and its groups. On our 4.14.7 lab the second command prints six lines for 5715, including 40112 at line 102 of 0280-attack_rules.xml. A sibling at a higher level that also matches your event will win it.

Sequences, not lines. Paste the real sequence into one wazuh-logtest session (the failures and then the success, or the five events a composite rule needs), not only the last line.

What we did not measure

One version (4.14.7), a single-node manager, and mostly if_sid. Not measured: if_group, decoders, clusters, reloading rules through the API, or how often restarts happen on a real system. The logtest counts in reason 3 come from one session per case. The flood numbers come from one agent reading a file, not syslog over the network.

Free: Rule Doctor Lite, a read-only script that lists the custom rules that never fired and the likely reason. Run after a restart, it reads the 7617/7619 warnings in ossec.log, and on our lab it flagged both in-file cases above.

If a rule loads and never fires and you would rather not dig into it yourself, send us the rule, your Wazuh version and a sample event: we find why, then write and test the fix on that version. USD 490 per rule case, paid only after it runs clean on your side. vct.atkvn.com/#fix-pack

Dong Nguyen, ATK New Technology

📰 Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.