146 lines
5.3 KiB
Java
146 lines
5.3 KiB
Java
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Test for esp-idf-0002: CWE-312 HTTP Digest auth password logged verbatim
|
|
* in components/esp_http_client/lib/http_auth.c http_auth_digest().
|
|
*
|
|
* Pattern:
|
|
* ESP_LOGD(TAG, "%s %s %s %s", "Digest", username, auth_data->realm, password)
|
|
*
|
|
* During HTTP Digest authentication setup the plaintext password is emitted
|
|
* to serial/JTAG when debug logging is active. Unlike Basic Auth the password
|
|
* should never leave the chip except as part of the one-way digest hash.
|
|
*
|
|
* Fix: remove the offending ESP_LOGD line. Logging the digest hash result
|
|
* (ha1/response) would be safe; logging the raw password is not.
|
|
*
|
|
* Compile and run (no build tool required):
|
|
* javac defects/esp-idf-0002/test/EspIdf0002Test.java -d /tmp/esp-idf-0002
|
|
* java -cp /tmp/esp-idf-0002 EspIdf0002Test
|
|
*/
|
|
public class EspIdf0002Test {
|
|
|
|
private static int passed = 0;
|
|
private static int failed = 0;
|
|
|
|
// --- Simulated log collector ---
|
|
|
|
static class LogCollector {
|
|
private final List<String> lines = new ArrayList<>();
|
|
|
|
void logd(String tag, String fmt, Object... args) {
|
|
lines.add(String.format(fmt, args));
|
|
}
|
|
|
|
boolean containsText(String text) {
|
|
for (String line : lines) {
|
|
if (line.contains(text)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int size() { return lines.size(); }
|
|
}
|
|
|
|
/** Trivial stand-in for MD5 hex — actual hash value irrelevant for this test. */
|
|
static String mockMd5(String input) {
|
|
return Integer.toHexString(input.hashCode() & 0x7fffffff);
|
|
}
|
|
|
|
// --- Defective: logs username + realm + password ---
|
|
|
|
static String computeDigestDefective(
|
|
String username, String realm, String password, LogCollector log) {
|
|
String ha1 = mockMd5(username + ":" + realm + ":" + password);
|
|
log.logd("HTTP_AUTH", "%s %s %s %s", "Digest", username, realm, password); // CWE-312
|
|
String ha2 = mockMd5("GET:/api/data");
|
|
return mockMd5(ha1 + ":nonce123:" + ha2);
|
|
}
|
|
|
|
// --- Fixed: no password in any log statement ---
|
|
|
|
static String computeDigestFixed(
|
|
String username, String realm, String password, LogCollector log) {
|
|
String ha1 = mockMd5(username + ":" + realm + ":" + password);
|
|
// ESP_LOGD with password removed — fix for esp-idf-0002
|
|
String ha2 = mockMd5("GET:/api/data");
|
|
return mockMd5(ha1 + ":nonce123:" + ha2);
|
|
}
|
|
|
|
// --- Tests ---
|
|
|
|
static void testDefectiveLogsPassword() {
|
|
LogCollector log = new LogCollector();
|
|
computeDigestDefective("alice", "testrealm@host.com", "hunter2", log);
|
|
check("defective must log the password (confirms CWE-312 site is present)",
|
|
log.containsText("hunter2"));
|
|
}
|
|
|
|
static void testFixedDoesNotLogPassword() {
|
|
LogCollector log = new LogCollector();
|
|
computeDigestFixed("alice", "testrealm@host.com", "hunter2", log);
|
|
check("fixed must NOT log the HTTP auth password",
|
|
!log.containsText("hunter2"));
|
|
}
|
|
|
|
static void testDefectiveAlsoExposesUsername() {
|
|
LogCollector log = new LogCollector();
|
|
computeDigestDefective("alice", "testrealm@host.com", "hunter2", log);
|
|
// The combined log line is the real risk (username + realm + password together)
|
|
check("defective log line includes username (combined exposure risk)",
|
|
log.containsText("alice"));
|
|
}
|
|
|
|
static void testFixedProducesNoLogLines() {
|
|
LogCollector log = new LogCollector();
|
|
computeDigestFixed("alice", "testrealm@host.com", "hunter2", log);
|
|
check("fixed digest computation emits no log lines",
|
|
log.size() == 0);
|
|
}
|
|
|
|
static void testBothReturnSameDigest() {
|
|
LogCollector log1 = new LogCollector();
|
|
LogCollector log2 = new LogCollector();
|
|
String d1 = computeDigestDefective("bob", "realm", "s3cr3t", log1);
|
|
String d2 = computeDigestFixed("bob", "realm", "s3cr3t", log2);
|
|
check("fixed and defective produce identical digest response (fix is behaviour-neutral)",
|
|
d1.equals(d2));
|
|
}
|
|
|
|
static void testPasswordNotLeakedForMultipleRealms() {
|
|
String[] realms = {"api.example.com", "admin.local", "iot-gateway"};
|
|
for (String realm : realms) {
|
|
LogCollector log = new LogCollector();
|
|
computeDigestFixed("user", realm, "TopSecretPW", log);
|
|
check("password not logged for realm '" + realm + "'",
|
|
!log.containsText("TopSecretPW"));
|
|
}
|
|
}
|
|
|
|
// --- Harness ---
|
|
|
|
static void check(String desc, boolean cond) {
|
|
if (cond) {
|
|
System.out.println(" PASS: " + desc);
|
|
passed++;
|
|
} else {
|
|
System.out.println(" FAIL: " + desc);
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== EspIdf0002Test (CWE-312 HTTP auth password logged) ===\n");
|
|
|
|
testDefectiveLogsPassword();
|
|
testFixedDoesNotLogPassword();
|
|
testDefectiveAlsoExposesUsername();
|
|
testFixedProducesNoLogLines();
|
|
testBothReturnSameDigest();
|
|
testPasswordNotLeakedForMultipleRealms();
|
|
|
|
System.out.println("\n--- " + passed + " passed, " + failed + " failed ---");
|
|
if (failed > 0) System.exit(1);
|
|
}
|
|
}
|