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 ==="); } }