import java.util.*; /** * suricata-0001 — CWE-312: HTTP credential headers logged verbatim * * File: src/output-json-http.c, function: EveHttpLogJSONHeaders() * * Defect: When Suricata's eve-log HTTP module is configured with * dump-all-headers (both/request/response) or a custom field list * that includes "authorization", "proxy-authorization", "cookie", or * "set-cookie", the full header value is written verbatim to the JSON * eve.log. An attacker or insider with log access obtains plaintext * Bearer tokens, Basic auth credentials, session cookies, and proxy * passwords. The default suricata.yaml.in explicitly shows Authorization * as an example custom field with no redaction warning. * * Fix: EveHttpLogJSONHeaders() checks each header name against a static * credential denylist before writing the value. Matching headers emit * "[REDACTED]" instead of the raw header value. Non-credential headers * are unaffected. * * This Java test models the patched and unpatched selection logic as plain * string functions, confirms correctness, and demonstrates the exposure. */ public class SuricataHttpHeaderRedactTest { // ---- credential denylist from the patch ---- private static final Set CREDENTIAL_HEADERS = new HashSet<>(Arrays.asList( "authorization", "proxy-authorization", "cookie", "set-cookie" )); /** Patched behaviour: redact credential header values. */ static String patchedValue(String headerName, String headerValue) { if (CREDENTIAL_HEADERS.contains(headerName.toLowerCase(Locale.ROOT))) { return "[REDACTED]"; } return headerValue; } /** Unpatched behaviour: always log the real value (the defect). */ static String unpatchedValue(String headerName, String headerValue) { return headerValue; } // ---- correctness checks ---- static void assertRedacted(String header, String value) { String logged = patchedValue(header, value); assert "[REDACTED]".equals(logged) : "Expected [REDACTED] for header '" + header + "', got: " + logged; } static void assertNotRedacted(String header, String value) { String logged = patchedValue(header, value); assert value.equals(logged) : "Expected original value for header '" + header + "', got: " + logged; } public static void main(String[] args) { System.out.println("=== suricata-0001 CWE-312 header redaction test ==="); // 1. Credential headers are redacted by the patch. assertRedacted("Authorization", "Bearer eyJhbGciOiJIUzI1NiJ9.secret"); assertRedacted("Authorization", "Basic dXNlcjpwYXNzd29yZA=="); assertRedacted("Proxy-Authorization", "Basic dXNlcjpwYXNzd29yZA=="); assertRedacted("Cookie", "session=abc123; auth_token=xyz987"); assertRedacted("Set-Cookie", "session=abc123; HttpOnly; Secure"); System.out.println("Credential header redaction: PASS"); // 2. Case-insensitive matching (HTTP headers are case-insensitive). assertRedacted("AUTHORIZATION", "Bearer secret"); assertRedacted("authorization", "Bearer secret"); assertRedacted("COOKIE", "session=xyz"); assertRedacted("set-cookie", "id=foo"); System.out.println("Case-insensitive denylist match: PASS"); // 3. Non-credential headers pass through unchanged. assertNotRedacted("User-Agent", "Mozilla/5.0"); assertNotRedacted("Accept", "application/json"); assertNotRedacted("X-Forwarded-For", "203.0.113.42"); assertNotRedacted("Content-Type", "application/x-www-form-urlencoded"); assertNotRedacted("Host", "api.example.com"); System.out.println("Non-credential headers pass-through: PASS"); // 4. Demonstrate the unpatched defect: credential values are exposed. String[] credHeaders = {"Authorization", "Proxy-Authorization", "Cookie", "Set-Cookie"}; String secret = "Bearer top-secret-token-abc123"; int unpatchedLeaks = 0; int patchedLeaks = 0; for (String h : credHeaders) { if (!unpatchedValue(h, secret).equals("[REDACTED]")) unpatchedLeaks++; if (!patchedValue(h, secret).equals("[REDACTED]")) patchedLeaks++; } assert unpatchedLeaks == 4 : "Unpatched should leak all 4 credential headers, got " + unpatchedLeaks; assert patchedLeaks == 0 : "Patched should leak 0 credential headers, got " + patchedLeaks; System.out.println("Unpatched leaks " + unpatchedLeaks + "/4 credential headers (defect confirmed)"); System.out.println("Patched leaks " + patchedLeaks + "/4 credential headers (fix confirmed)"); // 5. Simulate eve.log JSON output for an Authorization header. // Unpatched: {"name": "authorization", "value": "Bearer top-secret"} // Patched: {"name": "authorization", "value": "[REDACTED]"} String bearerToken = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.signature"; String unpatchedJson = "{\"name\":\"authorization\",\"value\":\"" + unpatchedValue("Authorization", bearerToken) + "\"}"; String patchedJson = "{\"name\":\"authorization\",\"value\":\"" + patchedValue("Authorization", bearerToken) + "\"}"; assert unpatchedJson.contains(bearerToken) : "Unpatched JSON must contain the secret token"; assert !patchedJson.contains(bearerToken) : "Patched JSON must not contain the secret token"; assert patchedJson.contains("[REDACTED]") : "Patched JSON must contain [REDACTED]"; System.out.println("JSON output simulation: PASS"); System.out.println(" Unpatched: " + unpatchedJson.substring(0, Math.min(70, unpatchedJson.length())) + "..."); System.out.println(" Patched: " + patchedJson); System.out.println("=== suricata-0001 PASS ==="); } }