diff --git a/Cargo.lock b/Cargo.lock index f5d3f53e..983b177a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -502,9 +502,9 @@ dependencies = [ [[package]] name = "barbacane-waf" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fdc4f8d76d6ba9dda6ec2e1c88febc74793f4542475212110f4d90f48f143d" +checksum = "da037d22b11789d2fbc73239b0d83f5fa7b5c1866ff6e876d55a9c8c34361471" dependencies = [ "aho-corasick", "barbacane-libinjection", diff --git a/Cargo.toml b/Cargo.toml index 2f44e0e4..5933075a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -103,7 +103,7 @@ jsonschema = "0.26" # WASM runtime # SecLang/CRS engine. Pinned by revision while its API settles; moves to a # crates.io version when it stabilises (ADR-0031). -parapet = { package = "barbacane-waf", version = "0.1.0", features = ["serde"] } +parapet = { package = "barbacane-waf", version = "0.1.1", features = ["serde"] } wasmtime = { version = "47", features = ["cranelift", "pooling-allocator"] } wasmtime-wasi = { version = "47" } diff --git a/crates/barbacane/src/main.rs b/crates/barbacane/src/main.rs index f4f9a949..afdf0bba 100644 --- a/crates/barbacane/src/main.rs +++ b/crates/barbacane/src/main.rs @@ -1359,7 +1359,7 @@ impl Gateway { let mut request_matched: Vec = Vec::new(); if let Some(stage) = &self.waf { let waf_start = std::time::Instant::now(); - let (decision, inspection) = stage.inspect_request( + let (decision, mut inspection) = stage.inspect_request( &method_str, &uri_string, waf_protocol, @@ -1404,6 +1404,9 @@ impl Gateway { &route_path, "waf_blocked", ); + // Run phase 5 so a request-phase block is logged and + // correlated the same as one that reached the response. + inspection.run_logging(); self.emit_waf_audit( &inspection, &method_str, diff --git a/crates/barbacane/src/waf.rs b/crates/barbacane/src/waf.rs index 1bd02d5d..cc115118 100644 --- a/crates/barbacane/src/waf.rs +++ b/crates/barbacane/src/waf.rs @@ -274,10 +274,9 @@ impl WafInspection<'_> { /// rule could block. CRS phase-4 rules are mostly data-leakage detection: /// stack traces, SQL errors, source code and credentials in a response body. /// - /// Phases run in order and the engine stops phase processing after a - /// disruptive verdict, so a phase-3 block skips phases 4 and 5 and a - /// phase-4 block skips phase 5. Phase 5 is logging and correlation on the - /// non-blocked path. + /// A block in phase 3 skips phase 4, but phase 5 (logging and correlation) + /// runs on every transaction, including a blocked one, so its correlation + /// rules contribute to the audit record. pub fn inspect_response( &mut self, status: u16, @@ -304,6 +303,14 @@ impl WafInspection<'_> { self.tx.matched_ids() } + /// Run phase 5 (logging and correlation) on a request that blocked before + /// reaching response inspection, so a request-phase block is logged the + /// same as one that reached the response. The response path runs phase 5 + /// through `inspect_response`, so this is only for the request-block path. + pub fn run_logging(&mut self) { + self.tx.process_logging(); + } + /// The rules that matched and would appear in the audit log, in match /// order. Excludes `nolog` rules, which matched and scored but do not log. pub fn audit_matches(&self) -> Vec { @@ -650,7 +657,7 @@ SecRule ARGS "@rx attack" "id:11,phase:2,pass,nolog,msg:'silent'" /// The engine stops phase processing after a disruptive verdict, so phases /// after the blocking one do not run. #[test] - fn a_phase_three_block_stops_before_later_phases() { + fn a_phase_three_block_skips_phase_four_but_runs_phase_five() { let stage = stage(EngineMode::Blocking); let (_, mut inspection) = inspect(&stage, "/?q=fine"); let decision = inspection.inspect_response( @@ -665,8 +672,8 @@ SecRule ARGS "@rx attack" "id:11,phase:2,pass,nolog,msg:'silent'" "phase 4 must not run after a phase-3 block" ); assert!( - !matched.contains(&5), - "phase 5 must not run after a phase-3 block" + matched.contains(&5), + "phase 5 runs even after a phase-3 block" ); } }