java-topology/defects/squid-0003/test/SquidCredentialLogTest.java
russell@unturf.com 1edc2f5a93 squid: 2 defects (squid-0002 CWE-407, squid-0003 CWE-312); MOADs 0002/0003/0005 CLEAN
MOAD-0001 squid-0002: HttpHeader::removeConnectionHeaderEntries() O(H*C) per response hop.
strListIsMember() scans all C Connection tokens for each of H header entries. Fix: pre-build
unordered_set from Connection tokens once, probe O(1) per entry. 4.84x measured speedup at
H=200 headers / C=50 Connection tokens. Called per hop in removeHopByHopEntries().

MOAD-0004 squid-0003: CWE-312 credentials logged verbatim in debug output.
FtpGateway.cc loginParser() logs user:password at debug 9; basic/Config.cc decodeCleartext()
logs decoded cleartext at debug 9 AND logs full Authorization header at DBG_IMPORTANT (level 1,
always on); basic/UserRequest.cc startHelperLookup() logs user:password at debug 9.
Fix: replace credential values with redacted markers / length-only diagnostic info.

MOAD-0002: SquidConfig 571-line god object in 209 files, 1408 call sites — structural,
documented in defects/squid/scan/MOAD-RESULTS.md.
MOAD-0003: CLEAN (event-loop single-threaded, no thread_local for request context).
MOAD-0005: CLEAN (event-loop single-threaded, no concurrent cache race).
2026-03-31 19:55:05 -04:00

139 lines
6.3 KiB
Java

import java.util.*;
import java.util.regex.*;
/**
* Unit test for squid-0003: CWE-312 - FTP and Basic auth credentials logged
* verbatim in debug output.
*
* Affected files:
* src/clients/FtpGateway.cc - loginParser() logs user:password at debug 9
* src/auth/basic/Config.cc - decodeCleartext() logs cleartext at debug 9,
* logs Authorization header at DBG_IMPORTANT (level 1)
* src/auth/basic/UserRequest.cc - startHelperLookup() logs user:password at debug 9
*
* CWE-312: Cleartext Storage of Sensitive Information.
* When an operator enables "debug_options 9,9" (or even "29,4"), plaintext
* FTP passwords and decoded Basic auth credentials land in cache.log.
*
* Fix: Replace credential values with redacted markers in all debug statements.
*/
public class SquidCredentialLogTest {
// Simulate the defect: credential fields emitted in log messages
static String loginParserLogDefect(String login, String user, String password) {
// FtpGateway.cc line 402
return "IN : login=" + login + ", user=" + user + ", password=" + password;
}
static String basicDecodeLogDefect(String cleartext) {
// basic/Config.cc line 188
return "'" + cleartext + "'";
}
static String basicHelperLogDefect(String username, String passwd) {
// basic/UserRequest.cc line 105
return "'" + username + ":" + passwd + "'";
}
// Simulate the fix: redacted log messages
static String loginParserLogFix(String login, String user, String password) {
return "IN : login=[REDACTED], user=[user], password=[REDACTED]";
}
static String basicDecodeLogFix(String cleartext) {
return "decoded basic credentials (length " + cleartext.length() + ")";
}
static String basicHelperLogFix(String username, String passwd) {
return "looking up basic auth user '" + username + "' (password suppressed)";
}
// Pattern to detect credential exposure in a log line
static boolean containsCredential(String logLine, String password) {
return logLine.contains(password);
}
static boolean containsUsername(String logLine, String username) {
return logLine.contains(username + ":");
}
public static void main(String[] args) {
System.out.println("=== squid-0003: CWE-312 credential logging in FTP and Basic auth ===");
System.out.println();
String ftpUser = "ftpuser";
String ftpPassword = "s3cr3t!FTP";
String ftpLogin = ftpUser + ":" + ftpPassword;
String basicUser = "proxyuser";
String basicPass = "myP@ssw0rd";
String cleartext = basicUser + ":" + basicPass;
// --- Defect verification: passwords ARE in log lines ---
String defectFtpLog = loginParserLogDefect(ftpLogin, ftpUser, ftpPassword);
String defectDecodeLog = basicDecodeLogDefect(cleartext);
String defectHelperLog = basicHelperLogDefect(basicUser, basicPass);
assert containsCredential(defectFtpLog, ftpPassword) :
"Defect FTP log should contain password";
assert containsCredential(defectDecodeLog, basicPass) :
"Defect decode log should contain password";
assert containsCredential(defectHelperLog, basicPass) :
"Defect helper log should contain password";
assert containsUsername(defectHelperLog, basicUser) :
"Defect helper log should contain user:pass pattern";
System.out.println("Defect confirmed: passwords present in log lines");
System.out.println(" FTP log: " + defectFtpLog);
System.out.println(" Decode log: " + defectDecodeLog);
System.out.println(" Helper log: " + defectHelperLog);
System.out.println();
// --- Fix verification: passwords NOT in log lines ---
String fixFtpLog = loginParserLogFix(ftpLogin, ftpUser, ftpPassword);
String fixDecodeLog = basicDecodeLogFix(cleartext);
String fixHelperLog = basicHelperLogFix(basicUser, basicPass);
assert !containsCredential(fixFtpLog, ftpPassword) :
"Fix FTP log must NOT contain password, got: " + fixFtpLog;
assert !containsCredential(fixDecodeLog, basicPass) :
"Fix decode log must NOT contain password, got: " + fixDecodeLog;
assert !containsCredential(fixHelperLog, basicPass) :
"Fix helper log must NOT contain password, got: " + fixHelperLog;
assert !containsUsername(fixHelperLog, basicUser) :
"Fix helper log must NOT contain user:pass pattern, got: " + fixHelperLog;
// Fix logs must still be useful (contain non-sensitive context)
assert fixFtpLog.contains("[REDACTED]") :
"Fix FTP log should show redaction marker";
assert fixDecodeLog.contains("length") :
"Fix decode log should include length info for diagnostics";
assert fixHelperLog.contains(basicUser) && fixHelperLog.contains("suppressed") :
"Fix helper log should show username (not secret) and suppression note";
System.out.println("Fix verified: no passwords in redacted log lines");
System.out.println(" FTP log: " + fixFtpLog);
System.out.println(" Decode log: " + fixDecodeLog);
System.out.println(" Helper log: " + fixHelperLog);
System.out.println();
// --- Severity: DBG_IMPORTANT path (level 1, always logged) ---
// basic/Config.cc:191 logs Authorization header at DBG_IMPORTANT when
// bad characters are detected - this fires even without debug_options tuning
String authHeader = "Basic " + Base64.getEncoder().encodeToString(cleartext.getBytes());
String defectImportantLog = "WARNING: Bad characters in authorization header '" + authHeader + "'";
String fixImportantLog = "WARNING: Bad characters in Basic authorization header (base64 header suppressed for security)";
assert defectImportantLog.contains(authHeader) :
"Defect important log should contain auth header";
assert !fixImportantLog.contains(authHeader) :
"Fix important log must NOT contain auth header";
System.out.println("DBG_IMPORTANT path: PASS");
System.out.println(" Defect: " + defectImportantLog);
System.out.println(" Fix: " + fixImportantLog);
System.out.println();
System.out.println("=== squid-0003 PASS ===");
}
}