# ------------------------------------------------------------------------ # OWASP CRS logging series: per-transaction anomaly score summary # # CRS ships its own reporting rule (980170) that writes a line with all the # anomaly scores in phase 5. On ModSecurity v3 (libmodsecurity, which the # official NGINX container uses) that line never reaches the error log or # the audit log: v3 implements the `noauditlog` action by discarding the # message altogether. The result is that the single most useful log line # CRS produces is invisible on the most common deployment. # # This rule runs after all CRS rules (RESPONSE-999 is the last file # included) and writes a key=value summary that is trivial to parse with # Logstash, Grafana Alloy, or a one-line grep. It fires for every # transaction that scored at least one point at the detection paranoia # level, exactly like CRS's own reporting level 4. # # Rule IDs 1-99,999 are reserved for local use, so there is no risk of a # clash with CRS or with plugins. # ------------------------------------------------------------------------ SecRule TX:DETECTION_ANOMALY_SCORE "@gt 0" \ "id:99901,\ phase:5,\ pass,\ t:none,\ log,\ auditlog,\ msg:'CRS summary: inbound_blocking=%{tx.blocking_inbound_anomaly_score} inbound_detection=%{tx.detection_inbound_anomaly_score} inbound_pl=%{tx.inbound_anomaly_score_pl1}-%{tx.inbound_anomaly_score_pl2}-%{tx.inbound_anomaly_score_pl3}-%{tx.inbound_anomaly_score_pl4} inbound_threshold=%{tx.inbound_anomaly_score_threshold} outbound_blocking=%{tx.blocking_outbound_anomaly_score} outbound_detection=%{tx.detection_outbound_anomaly_score} outbound_threshold=%{tx.outbound_anomaly_score_threshold} sqli=%{tx.sql_injection_score} xss=%{tx.xss_score} rfi=%{tx.rfi_score} lfi=%{tx.lfi_score} rce=%{tx.rce_score} phpi=%{tx.php_injection_score} http=%{tx.http_violation_score} sess=%{tx.session_fixation_score} blocking_pl=%{tx.blocking_paranoia_level} detection_pl=%{tx.detection_paranoia_level}',\ tag:'crs-summary'"