diff --git a/defects/suricata-0001/patch/suricata-0001.patch b/defects/suricata-0001/patch/suricata-0001.patch new file mode 100644 index 000000000..a24ec243a --- /dev/null +++ b/defects/suricata-0001/patch/suricata-0001.patch @@ -0,0 +1,57 @@ +--- a/src/output-json-http.c ++++ b/src/output-json-http.c +@@ -310,6 +310,19 @@ static void EveHttpLogJSONHeaders( + SCJsonBuilder *js, uint32_t direction, htp_tx_t *tx, LogHttpFileCtx *http_ctx) + { ++ /* Credential denylist: headers whose values must never appear in logs verbatim. ++ * When dump-all-headers is enabled these headers are redacted to "[REDACTED]". ++ * When a custom field list explicitly includes one of these names the value is ++ * also redacted. The denylist covers the standard credential-bearing headers: ++ * Authorization, Proxy-Authorization, Cookie, Set-Cookie. ++ * CWE-312 (Cleartext Storage of Sensitive Information). ++ */ ++ static const char * const credential_headers[] = { ++ "authorization", ++ "proxy-authorization", ++ "cookie", ++ "set-cookie", ++ NULL, ++ }; ++ + const htp_headers_t *headers = direction & LOG_HTTP_REQ_HEADERS ? htp_tx_request_headers(tx) + : htp_tx_response_headers(tx); + char name[MAX_SIZE_HEADER_NAME] = {0}; +@@ -344,7 +357,22 @@ static void EveHttpLogJSONHeaders( + array_empty = false; + SCJbStartObject(js); + size_t size_name = htp_header_name_len(h) < MAX_SIZE_HEADER_NAME - 1 + ? htp_header_name_len(h) + : MAX_SIZE_HEADER_NAME - 1; + memcpy(name, htp_header_name_ptr(h), size_name); + name[size_name] = '\0'; + SCJbSetString(js, "name", name); +- size_t size_value = htp_header_value_len(h) < MAX_SIZE_HEADER_VALUE - 1 +- ? htp_header_value_len(h) +- : MAX_SIZE_HEADER_VALUE - 1; +- memcpy(value, htp_header_value_ptr(h), size_value); +- value[size_value] = '\0'; +- SCJbSetString(js, "value", value); ++ bool is_credential = false; ++ for (int ci = 0; credential_headers[ci] != NULL; ci++) { ++ if (strcasecmp(name, credential_headers[ci]) == 0) { ++ is_credential = true; ++ break; ++ } ++ } ++ if (is_credential) { ++ SCJbSetString(js, "value", "[REDACTED]"); ++ } else { ++ size_t size_value = htp_header_value_len(h) < MAX_SIZE_HEADER_VALUE - 1 ++ ? htp_header_value_len(h) ++ : MAX_SIZE_HEADER_VALUE - 1; ++ memcpy(value, htp_header_value_ptr(h), size_value); ++ value[size_value] = '\0'; ++ SCJbSetString(js, "value", value); ++ } + SCJbClose(js); + } diff --git a/defects/suricata-0001/test/SuricataHttpHeaderRedactTest.class b/defects/suricata-0001/test/SuricataHttpHeaderRedactTest.class new file mode 100644 index 000000000..6c130f5a8 Binary files /dev/null and b/defects/suricata-0001/test/SuricataHttpHeaderRedactTest.class differ diff --git a/defects/suricata-0001/test/SuricataHttpHeaderRedactTest.java b/defects/suricata-0001/test/SuricataHttpHeaderRedactTest.java new file mode 100644 index 000000000..19b3855d2 --- /dev/null +++ b/defects/suricata-0001/test/SuricataHttpHeaderRedactTest.java @@ -0,0 +1,122 @@ +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 ==="); + } +} diff --git a/defects/suricata/scan b/defects/suricata/scan new file mode 100644 index 000000000..92a203b55 --- /dev/null +++ b/defects/suricata/scan @@ -0,0 +1,44 @@ +suricata — 5-MOAD scan results + +Target: https://github.com/OISF/suricata (depth=1) +Language: C +Date: 2026-03-31 +Scanned by: agent blackops + +MOAD-0001 CWE-407 (linear scan in loop): + CLEAN. Suricata uses bitarrays (SigGroupHead sig_array), hash tables + (SigGroupHeadHashTable, MpmInitHash, THashLookupFromHash with HRLOCK), + and red-black trees (TCPSEG, SBB) throughout hot paths. Merge-sort + (O(N log N)) is used for signature ordering. No O(N^2) per-packet path + found. The probing parser port list scan is O(P) with P < 50 entries. + +MOAD-0002 Intertangle: + CLEAN. Subsystems are cleanly separated. The detection engine uses a + per-tenant DetectEngineCtx. App-layer parsers run in their own context. + Global registration lists (g_app_inspect_engines etc.) are write-once + at startup and read-only thereafter. No god object coupling independent + subsystems through shared mutable state. + +MOAD-0003 Leaked Context: + CLEAN. All thread_local variables hold thread-scoped data: + thread_pkt_pool (packet pool), t_thread_name (thread identifier), + pcap_file_thread (output file handle), tcp_pool_cache (segment pool), + locks[] (lock profiling). None holds request-scoped identity that + could leak across flow or packet boundaries. + +MOAD-0004 CWE-312 (credentials logged verbatim): + DEFECT — see defects/suricata-0001/ for patch and test. + EveHttpLogJSONHeaders() in src/output-json-http.c logs all header values + verbatim when dump-all-headers is enabled or when a custom field list + includes credential headers. The default suricata.yaml.in shows + "Authorization" as a custom field example with no warning. At runtime: + Authorization (Bearer/Basic tokens), Proxy-Authorization, Cookie + (session tokens), and Set-Cookie values appear in eve.log in cleartext. + +MOAD-0005 Thundering Herd: + CLEAN. THashLookupFromHash acquires a per-row HRLOCK before any read + or write. THashAdd uses the same HRLOCK. No get+null+compute+put + race window exists. Flow hash (FlowGetFlowFromHash) also uses per-bucket + mutex (FBLOCK_LOCK). No concurrent cache unsynchronized race found. + +Summary: 1 defect (suricata-0001 MOAD-0004 CWE-312 MEDIUM)