java-topology/defects/pgbouncer-0001/test/PgBouncerScramSecretLogTest.java
russell@unturf.com 6b80a87270 snort3: CWE-407 CHP match_tally O(M*T); pgbouncer: CWE-312 SCRAM secret logged
snort3-0002: chp_add_candidate_to_tally() in http_url_patterns.cc calls std::find_if
over CHPMatchTally vector for each Aho-Corasick HTTP key-pattern match callback,
O(M*T) per packet. Fix: add unordered_map index to ChpMatchDescriptor for O(1) lookup.
48x op-count reduction at T=100/M=20. 3/3 PASS.

pgbouncer-0001: scram_client_first() logs user->passwd (SCRAM verifier or plaintext
password) at slog_debug level, CWE-312. Fix: remove the log line. 5/5 PASS.

pgbouncer MOAD-0002/0003/0005 CLEAN (single-threaded libevent loop).
snort3 MOAD-0002/0003/0004/0005 CLEAN.
2026-03-31 20:06:15 -04:00

132 lines
6.1 KiB
Java

import java.util.*;
/**
* pgbouncer-0001: CWE-312 — SCRAM verifier logged verbatim at debug level
*
* Source: src/client.c, function scram_client_first()
* Line: slog_debug(client, "stored secret = \"%s\"", user->passwd);
*
* Defect: During SCRAM-SHA-256 authentication, PgBouncer logs the user's stored
* secret (user->passwd) at slog_debug level. Depending on the auth_type configured,
* user->passwd may contain:
* - A plaintext password (auth_type=plain)
* - A SCRAM-SHA-256 verifier: "SCRAM-SHA-256$<iterations>:<salt>$<StoredKey>:<ServerKey>"
*
* Even the SCRAM verifier is sensitive: it can be used in an offline dictionary attack
* or, for plaintext passwords, directly as the credential. Logging it to a debug log
* file violates CWE-312 (Cleartext Storage of Sensitive Information).
*
* Fix: Remove the slog_debug line. The log event type ("SCRAM client-first received")
* is still present on line 1119; no diagnostic value is lost.
*
* CVE-applicable: Yes — debug log file exposure of authentication credential.
* Severity: MEDIUM-HIGH (requires debug log access, but many ops enable debug logging
* during troubleshooting, leaving credential in log files indefinitely).
*/
public class PgBouncerScramSecretLogTest {
// Simulate the log capture system
static List<String> capturedLogs = new ArrayList<>();
static void slog_debug(String context, String fmt, Object... args) {
capturedLogs.add(String.format("[DEBUG][" + context + "] " + fmt, args));
}
// Simulated PgCredentials
static class PgCredentials {
String name;
String passwd;
boolean mock_auth;
PgCredentials(String name, String passwd) {
this.name = name;
this.passwd = passwd;
this.mock_auth = false;
}
}
// --- DEFECTIVE: logs stored secret ---
static void scram_client_first_defective(String clientContext, PgCredentials user, String clientFirstMessage) {
slog_debug(clientContext, "SCRAM client-first-message = \"%s\"", clientFirstMessage);
if (!user.mock_auth) {
// DEFECT: logs the SCRAM verifier or plaintext password
slog_debug(clientContext, "stored secret = \"%s\"", user.passwd);
}
// ... rest of SCRAM processing
}
// --- FIXED: does not log stored secret ---
static void scram_client_first_fixed(String clientContext, PgCredentials user, String clientFirstMessage) {
slog_debug(clientContext, "SCRAM client-first-message = \"%s\"", clientFirstMessage);
if (!user.mock_auth) {
// Fixed: no credential logging. Comment in code:
// /* Do not log user->passwd: it holds the SCRAM verifier or plaintext password (CWE-312). */
}
// ... rest of SCRAM processing
}
static void testDefectiveLogsSecret() {
capturedLogs.clear();
PgCredentials user = new PgCredentials("alice",
"SCRAM-SHA-256$4096:c2FsdHNhbHRzYWx0$StoredKeyHere:ServerKeyHere");
scram_client_first_defective("client:127.0.0.1:5432", user, "n,,n=alice,r=clientnonce");
boolean secretFound = capturedLogs.stream().anyMatch(line -> line.contains("stored secret"));
assert secretFound : "Defective impl should log 'stored secret'";
System.out.println("PASS defective: secret IS logged: "
+ capturedLogs.stream().filter(l -> l.contains("stored secret")).findFirst().orElse("?"));
}
static void testFixedDoesNotLogSecret() {
capturedLogs.clear();
PgCredentials user = new PgCredentials("alice",
"SCRAM-SHA-256$4096:c2FsdHNhbHRzYWx0$StoredKeyHere:ServerKeyHere");
scram_client_first_fixed("client:127.0.0.1:5432", user, "n,,n=alice,r=clientnonce");
boolean secretFound = capturedLogs.stream().anyMatch(line -> line.contains("stored secret"));
assert !secretFound : "Fixed impl must NOT log 'stored secret', got: " + capturedLogs;
System.out.println("PASS fixed: secret is NOT logged (logs: " + capturedLogs.size() + " line(s))");
}
static void testFixedStillLogsDiagnostic() {
capturedLogs.clear();
PgCredentials user = new PgCredentials("bob", "SCRAM-SHA-256$4096:abc$defgh:ijklm");
scram_client_first_fixed("client:127.0.0.1:5433", user, "n,,n=bob,r=bobnonce");
boolean clientFirstLogged = capturedLogs.stream().anyMatch(line -> line.contains("client-first-message"));
assert clientFirstLogged : "Fixed impl must still log SCRAM client-first-message event";
System.out.println("PASS fixed: diagnostic 'client-first-message' still logged");
}
static void testPlaintextPasswordNotLogged() {
capturedLogs.clear();
PgCredentials user = new PgCredentials("carol", "supersecretpassword123");
scram_client_first_fixed("client:192.168.1.1:5432", user, "n,,n=carol,r=carolnonce");
boolean plainPassFound = capturedLogs.stream().anyMatch(line ->
line.contains("supersecretpassword123"));
assert !plainPassFound : "Fixed impl must NOT log plaintext passwords";
System.out.println("PASS fixed: plaintext password NOT logged");
}
static void testMockAuthNotLogged() {
capturedLogs.clear();
PgCredentials user = new PgCredentials("mockuser", "not-a-real-secret");
user.mock_auth = true;
// Both defective and fixed skip the branch when mock_auth=true
scram_client_first_defective("client:10.0.0.1:5432", user, "n,,n=mockuser,r=mocknonce");
boolean secretFound = capturedLogs.stream().anyMatch(line -> line.contains("stored secret"));
assert !secretFound : "mock_auth=true: secret branch should be skipped";
System.out.println("PASS mock-auth: secret branch skipped for mock_auth=true");
}
public static void main(String[] args) {
System.out.println("=== pgbouncer-0001: CWE-312 SCRAM verifier logged at debug ===");
testDefectiveLogsSecret();
testFixedDoesNotLogSecret();
testFixedStillLogsDiagnostic();
testPlaintextPasswordNotLogged();
testMockAuthNotLogged();
System.out.println("ALL PASS");
}
}