Testing correlation logic¶
Why correlation rules need systematic testing¶
Correlation rules are complex. They depend on multiple decoders extracting fields correctly, parent rules firing in the right sequence, timeframes catching events that might be seconds or minutes apart, and field values matching across different log sources. Any failure in this chain breaks correlation silently. You think you are detecting attacks but your rules never trigger.
The Red Lantern simulator provides ground truth for testing correlation. You know exactly when attacks occur, what logs are generated, and what your rules should detect. This lets you validate correlation logic before deploying to production where ground truth is unavailable and mistakes are expensive.
Using medium and advanced scenarios¶
Easy scenarios test basic detection. Medium and advanced scenarios test correlation because they involve multi-stage attacks spanning multiple log sources.
Playbook 2 (medium difficulty)¶
Tests correlation of:
TACACS authentication with ROA creation
ROA publication with validator synchronisation
Validation enforcement testing (frequency-based detection)
Run playbook 2 and identify correlation opportunities:
python -m simulator.cli simulator/scenarios/medium/playbook2/scenario.yaml --mode training --output cli
Training mode shows attack steps:
SCENARIO: T+60 - Credential use for RIR portal
SCENARIO: T+120 - Fraudulent ROA creation request
SCENARIO: T+2400 - Fraudulent ROA published
SCENARIO: T+3000 - Validation enforcement mapping begins
Your correlation rules should trigger at these timestamps. If they do not, either your rules are wrong or your decoders are broken.
Playbook 3 (advanced difficulty)¶
Tests correlation of:
BMP announcements with router logs
Router logs with RPKI validator confirmations
Traffic interception with forwarding establishment
Multi-source confirmation of attack success
Run playbook 3 and validate full correlation chain:
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode training --output cli
Expected correlation points:
SCENARIO: T+60 - Hijack announcement (BMP + router should correlate)
SCENARIO: T+180 - RPKI validation begins (validators + router should correlate)
SCENARIO: T+300 - Traffic interception confirmed (multiple sources correlate)
Validating event sequence detection¶
Correlation rules detect sequences. Testing validates that sequences trigger rules and non-sequences do not.
Test case 1: Complete sequence¶
Run playbook 3 which contains a complete attack sequence:
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --output cli | nc wazuh-server 514
# Wait for processing
sleep 10
# Check if correlation rule fired
grep "100902" /var/ossec/logs/alerts/alerts.json
Expected result: Alert with rule ID 100902 indicating full correlation chain detected.
If no alert appears:
Check each parent rule individually (
grep "100900\|100901\|100902")Verify decoders extract fields correctly
Check timeframes are adequate
Confirm field names match across correlation stages
Test case 2: Partial sequence (should not trigger final correlation)¶
Create a modified scenario with only BMP and router logs, no RPKI validator logs:
# Generate full logs
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --output cli > full.log
# Extract only BMP and router logs (omit validators)
grep -E "^BMP ROUTE:|edge-router-01 BGP" full.log | nc wazuh-server 514
# Wait for processing
sleep 10
# Check alerts
grep "100902" /var/ossec/logs/alerts/alerts.json
Expected result: No alert with rule ID 100902 because the sequence is incomplete. Rules 100900 and 100901 might fire (BMP and router correlation) but 100902 should not (requires validator confirmation).
If 100902 fires anyway, your correlation logic is wrong. It is triggering on incomplete sequences.
Test case 3: Events out of order¶
Correlation rules should respect event ordering. If router logs appear before BMP logs, correlation should fail:
# Generate logs
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --output cli > full.log
# Reverse order: router logs first, then BMP
grep "edge-router-01 BGP" full.log | nc wazuh-server 514
sleep 2
grep "^BMP ROUTE:" full.log | nc wazuh-server 514
# Check alerts
sleep 10
grep "100901" /var/ossec/logs/alerts/alerts.json
Expected result: Rule 100901 should NOT fire because router logs arrived before BMP logs. Wazuh correlation is temporal; parent rules must fire first.
If 100901 fires, you have discovered a timing issue or your correlation rule is not properly using if_matched_sid.
Test case 4: Events outside timeframe¶
Events that occur too far apart should not correlate:
# Send BMP log
grep "^BMP ROUTE:" full.log | nc wazuh-server 514
# Wait longer than correlation timeframe (e.g., 5 minutes if timeframe is 180 seconds)
sleep 300
# Send router log
grep "edge-router-01 BGP" full.log | nc wazuh-server 514
# Check alerts
sleep 10
grep "100901" /var/ossec/logs/alerts/alerts.json
Expected result: Rule 100901 should NOT fire because router logs arrived outside the 180-second correlation window.
If 100901 fires, your timeframe is too long or Wazuh’s correlation state has not expired properly.
Measuring correlation accuracy¶
Correlation accuracy has two dimensions: sensitivity (detecting actual attacks) and specificity (not triggering on benign activity).
Sensitivity testing with attack scenarios¶
Run all playbook scenarios and measure detection rate:
#!/bin/bash
# test_sensitivity.sh
scenarios=(
"simulator/scenarios/medium/playbook2/scenario.yaml"
"simulator/scenarios/advanced/playbook3/scenario.yaml"
)
for scenario in "${scenarios[@]}"; do
scenario_name=$(basename $(dirname "$scenario"))
# Clear alerts
> /var/ossec/logs/alerts/alerts.json
# Run scenario
python -m simulator.cli "$scenario" --mode practice --output cli | nc wazuh-server 514
# Wait for processing
sleep 15
# Check if correlation rules fired
if grep -q "100902\|100751\|100762" /var/ossec/logs/alerts/alerts.json; then
echo "$scenario_name: DETECTED"
else
echo "$scenario_name: MISSED"
fi
done
Expected result: All scenarios should show “DETECTED”. If any show “MISSED”, your correlation rules have sensitivity problems (false negatives).
Specificity testing with background noise¶
Run scenarios with background noise and measure false positive rate:
# Generate logs with high background noise
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --background --bgp-noise-rate 10.0 --output cli | nc wazuh-server 514
# Wait for processing
sleep 30
# Count total correlation alerts
total=$(grep -c "correlation" /var/ossec/logs/alerts/alerts.json)
# Count attack-related alerts (rule IDs for actual attacks)
attacks=$(grep -cE "100902|100751|100762" /var/ossec/logs/alerts/alerts.json)
# False positives
false_positives=$((total - attacks))
echo "Total correlation alerts: $total"
echo "Attack alerts: $attacks"
echo "False positives: $false_positives"
echo "False positive rate: $(awk "BEGIN {printf \"%.2f\", ($false_positives / $total) * 100}")%"
Acceptable false positive rates:
Under 10%: Excellent
10-20%: Good
20-30%: Acceptable for low-volume environments
Over 30%: Too high, rules need tuning
If false positive rate exceeds 30%, review correlation rules. Common causes:
Timeframes too long (correlating unrelated events)
Field matching too broad (catching benign activity)
Insufficient context (not checking all required conditions)
Measuring time to detection¶
Correlation rules should detect attacks quickly. Measure time from attack initiation to alert:
# Run scenario in training mode to see attack timing
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode training --output cli | tee scenario.log | nc wazuh-server 514
# Extract attack initiation time from training output
attack_time=$(grep "SCENARIO: T+60 - Hijack announcement" scenario.log | awk '{print $2}')
# Wait for processing
sleep 10
# Extract alert time from Wazuh logs
alert_time=$(grep "100902" /var/ossec/logs/alerts/alerts.json | jq -r '.timestamp' | head -1)
# Calculate detection delay
echo "Attack occurred: $attack_time"
echo "Alert generated: $alert_time"
# (Manual calculation of difference)
Target detection times:
Critical alerts (hijacks): under 60 seconds
High severity (ROA manipulation): under 5 minutes
Medium severity (reconnaissance): under 15 minutes
If detection takes longer, investigate:
Log ingestion delays (slow syslog forwarding)
Wazuh processing backlogs (check queue sizes)
Correlation timeframes (too narrow to catch sequences)
Testing with realistic traffic patterns¶
The simulator’s background noise is synthetic. Production networks have different traffic patterns. Test correlation rules with production-like traffic when possible.
Replaying production logs with injected attacks¶
If you have anonymised production logs, inject simulator output:
# Generate attack logs
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --output cli > attack.log
# Combine with production logs
cat production_bgp.log attack.log production_bgp.log | sort | nc wazuh-server 514
This tests whether correlation rules work when attacks are buried in real traffic patterns. The simulator’s background noise approximates real traffic but cannot perfectly replicate your specific environment.
Testing during different times of day¶
Network traffic patterns vary by time. Test correlation during:
Peak hours (high legitimate traffic might create false positives)
Maintenance windows (legitimate changes might look like attacks)
Off-hours (lower traffic might affect statistical baselines)
Run the same scenario at different times and compare results:
# Run at 0200 (low traffic)
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --output cli | nc wazuh-server 514
# Run at 1400 (peak traffic)
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --background --bgp-noise-rate 20.0 --output cli | nc wazuh-server 514
Correlation should work equally well at both times. If detection fails during peak hours, your rules are sensitive to traffic volume.
Automated correlation testing¶
Manual testing catches obvious problems. Automated testing catches regressions when you modify rules.
Basic test script¶
#!/bin/bash
# test_correlation.sh
set -e
WAZUH_ALERTS="/var/ossec/logs/alerts/alerts.json"
FAIL=0
test_scenario() {
local scenario=$1
local expected_rule=$2
local description=$3
echo "Testing: $description"
# Clear alerts
> "$WAZUH_ALERTS"
# Run scenario
python -m simulator.cli "$scenario" --mode practice --output cli | nc localhost 514
# Wait for processing
sleep 10
# Check for expected alert
if grep -q "\"id\": \"$expected_rule\"" "$WAZUH_ALERTS"; then
echo " PASS: Rule $expected_rule triggered"
else
echo " FAIL: Rule $expected_rule did not trigger"
FAIL=1
fi
}
# Test playbook 2 correlation
test_scenario \
"simulator/scenarios/medium/playbook2/scenario.yaml" \
"100751" \
"Playbook 2: Fraudulent ROA detection"
# Test playbook 3 correlation
test_scenario \
"simulator/scenarios/advanced/playbook3/scenario.yaml" \
"100902" \
"Playbook 3: Multi-source hijack correlation"
# Exit with error if any test failed
if [ $FAIL -eq 1 ]; then
echo "Some tests failed"
exit 1
fi
echo "All tests passed"
exit 0
Run this after modifying correlation rules to ensure changes do not break existing detection.
Integration with CI/CD¶
Add correlation testing to your CI/CD pipeline:
# .gitlab-ci.yml
test_correlation:
stage: test
script:
- ./scripts/deploy_test_wazuh.sh
- ./scripts/test_correlation.sh
artifacts:
reports:
junit: correlation_test_results.xml
This prevents deploying broken correlation rules to production.
Regression testing¶
When adding new correlation rules, ensure they do not interfere with existing rules:
# Run all scenarios before changes
./test_correlation.sh > baseline.txt
# Make rule changes
vim /var/ossec/etc/rules/local_rules.xml
systemctl restart wazuh-manager
# Run all scenarios after changes
./test_correlation.sh > updated.txt
# Compare results
diff baseline.txt updated.txt
If new rules break existing correlation (detection rate drops), you have introduced a regression.
Debugging correlation failures¶
When correlation rules do not trigger, systematic debugging identifies the problem.
Step 1: Verify parent rules fire¶
Check if parent rules trigger:
# Run scenario
python -m simulator.cli simulator/scenarios/advanced/playbook3/scenario.yaml --mode practice --output cli | nc wazuh-server 514
# Check all rules in correlation chain
grep -E "100900|100901|100902" /var/ossec/logs/alerts/alerts.json
If parent rules do not fire, the problem is decoders or rule conditions, not correlation logic.
Step 2: Check field extraction¶
Use wazuh-logtest to verify decoders extract fields:
echo 'BMP ROUTE: prefix 203.0.113.128/25 AS_PATH [65001, 64513] NEXT_HOP 198.51.100.254 ORIGIN_AS 64513' | /var/ossec/bin/wazuh-logtest
Expected output should show extracted fields:
bmp.prefix: 203.0.113.128/25
bmp.as_path: 65001, 64513
bmp.next_hop: 198.51.100.254
bmp.origin_as: 64513
If fields are not extracted, decoder regex is wrong.
Step 3: Verify timeframe adequacy¶
Check timestamps of parent rule alerts:
grep "100900" /var/ossec/logs/alerts/alerts.json | jq '.timestamp'
grep "100901" /var/ossec/logs/alerts/alerts.json | jq '.timestamp'
Calculate time difference. If difference exceeds correlation timeframe, rules cannot correlate.
Step 4: Check field matching¶
Verify field values match across correlation stages:
# Extract prefix from parent rule alert
prefix_parent=$(grep "100900" /var/ossec/logs/alerts/alerts.json | jq -r '.data.bmp.prefix')
# Extract prefix from child rule alert
prefix_child=$(grep "100901" /var/ossec/logs/alerts/alerts.json | jq -r '.data.router.prefix')
echo "Parent prefix: $prefix_parent"
echo "Child prefix: $prefix_child"
If prefixes do not match (e.g., 203.0.113.128/25 vs 203.0.113.128 255.255.255.128), field normalisation is needed.
Next steps¶
You now understand systematic testing of correlation logic using simulator scenarios. Proceed to Correlation in different platforms to learn how to implement correlation in Splunk, Elastic, Sentinel, and custom engines when Wazuh’s capabilities are insufficient for your correlation requirements.
Testing is ongoing, not one-time. As attacks evolve and the simulator adds new scenarios, retest correlation rules to ensure detection remains effective. The Scarlet Semaphore does not stand still. Neither should your validation process.