feat: add unit, integration, and functional test coverage for all 9 MOADs
Each MOAD now has a synthetic defective specimen and fixed specimen proven from first principles across three test tiers: Unit (tests/unit/Moad000X*.java): - Correctness: defective and fixed produce identical functional output - Defect behavior: defective specimen exhibits the defect (measurable) - Fix behavior: fixed specimen eliminates the defect Integration (tests/integration/AllMoadsIntegrationTest.java): - All 9 MOADs proven at medium scale (N=500-2000) - MOAD-0001: O(N^2) vs O(N) list scan at N=1000 - MOAD-0002: 500 sessions trample each other (defective) vs coexist (fixed) - MOAD-0003: 250 anonymous requests leak auth identity (defective) vs zero (fixed) - MOAD-0004: 3000 credential exposures across 1000 requests (defective) vs zero (fixed) - MOAD-0005: 500 computes for 500 concurrent misses vs exactly 1 - MOAD-0006: all 500 passwords extractable from DB (defective) vs unextractable (fixed) - MOAD-0007: N=2000 spatial objects, defective visits all 2000 vs O(log N + k) - MOAD-0009: 990 wasted firings for 1000 ticks / 10 events vs zero waste - MOAD-0011: 10240 NFA steps vs 13 steps on N=12 adversarial input (788x) Functional (tests/functional/AllMoadsFunctionalTest.java): - MOAD-0005: real-thread contention proves herd (defective >1 compute, fixed exactly 1) - MOAD-0007: N=50000 spatial objects, 50M defective probes vs 516K fixed (97x speedup) - MOAD-0009: 10000 ticks / 10 events, 9990 wasted firings vs zero (1000x ratio) - MOAD-0011: N=16 adversarial, 163840 defective steps vs 17 fixed (9638x ratio) Support algorithms (tests/support/Moad000X*.java): - Moad0002Algorithm: shared mutable global state (DefectiveAudioSystem / FixedAudioSystem + Context) - Moad0003Algorithm: ThreadLocal not cleared (handleDefective / handleFixed with finally) - Moad0004Algorithm: HTTP headers logged verbatim (logDefective / logFixed with CREDENTIAL_HEADERS denylist) - Moad0005Algorithm: get+null+compute+put (DefectiveCache HashMap / FixedCache ConcurrentHashMap.computeIfAbsent) - Moad0006Algorithm: Base64 password storage (DefectiveCredentialStore / FixedCredentialStore SHA-256+salt) - Moad0007Algorithm: linear spatial scan (queryDefective list / queryFixed sorted array + binary search) - Moad0009Algorithm: timer-driven polling (runDefectiveScheduler / runFixedEventDriven) - Moad0011Algorithm: PCRE nested quantifiers (matchDefective backtracking NFA / matchFixed linear NFA) Makefile: added unit-moad-0002 through unit-moad-0011 targets, integration-all-moads, functional-all-moads. integration and functional targets now depend on all-MOADs variants.
This commit is contained in:
parent
1f88aa0374
commit
4888153e40
19 changed files with 3359 additions and 5 deletions
130
tests/support/Moad0006Algorithm.java
Normal file
130
tests/support/Moad0006Algorithm.java
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
package support;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MOAD-0006: CWE-257 — A Glass Safe.
|
||||
*
|
||||
* Defect: credentials stored in recoverable form — plaintext, Base64 encoding,
|
||||
* or reversible encryption. Anyone with read access to the credential store
|
||||
* (DB dump, log file, backup) can reconstruct the original secret.
|
||||
*
|
||||
* Variant: Mailman 2.x actively emails list passwords to members on a monthly
|
||||
* schedule, because it stores them in recoverable form.
|
||||
*
|
||||
* Fix: one-way hashing with salt (SHA-256 + random salt shown here; bcrypt or
|
||||
* Argon2 preferred in production). The stored value cannot be reversed to
|
||||
* recover the original credential — only verification (hash-and-compare) works.
|
||||
*
|
||||
* Scanner detects: Base64.encode/decode on password-shaped values, or direct
|
||||
* string equality comparison between a stored field and user-supplied input.
|
||||
*/
|
||||
public class Moad0006Algorithm {
|
||||
|
||||
// ── Defective: Base64 storage (reversible) ────────────────────────────────
|
||||
|
||||
/**
|
||||
* Credential store that encodes passwords with Base64.
|
||||
* DEFECT: Base64 is an encoding, not a hash. decode() recovers the original.
|
||||
*/
|
||||
public static final class DefectiveCredentialStore {
|
||||
private final Map<String, String> store = new HashMap<>();
|
||||
|
||||
/** Store password encoded as Base64. DEFECT: reversible. */
|
||||
public void storePassword(String username, String password) {
|
||||
store.put(username,
|
||||
Base64.getEncoder().encodeToString(
|
||||
password.getBytes(StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
/** Verify by encoding the candidate and comparing. */
|
||||
public boolean verify(String username, String password) {
|
||||
String stored = store.get(username);
|
||||
if (stored == null) return false;
|
||||
String candidate = Base64.getEncoder().encodeToString(
|
||||
password.getBytes(StandardCharsets.UTF_8));
|
||||
return stored.equals(candidate);
|
||||
}
|
||||
|
||||
/**
|
||||
* DEFECT: attacker with DB read access calls this to recover original password.
|
||||
* Returns the plaintext password — the glass safe opens.
|
||||
*/
|
||||
public String extractPassword(String username) {
|
||||
String stored = store.get(username);
|
||||
if (stored == null) return null;
|
||||
return new String(Base64.getDecoder().decode(stored), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
/** Exposes raw stored value (what a DB dump would show). */
|
||||
public String rawStored(String username) {
|
||||
return store.get(username);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Fixed: SHA-256 + salt (one-way) ──────────────────────────────────────
|
||||
|
||||
private static final SecureRandom RNG = new SecureRandom();
|
||||
|
||||
/** Generate a fresh 16-byte random salt. */
|
||||
public static byte[] generateSalt() {
|
||||
byte[] salt = new byte[16];
|
||||
RNG.nextBytes(salt);
|
||||
return salt;
|
||||
}
|
||||
|
||||
/** Hash password with salt using SHA-256. */
|
||||
public static byte[] sha256(String password, byte[] salt) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(salt);
|
||||
return md.digest(password.getBytes(StandardCharsets.UTF_8));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException(e); // SHA-256 always available in Java
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Credential store that hashes passwords with SHA-256 + random salt.
|
||||
* FIX: no extraction method exists — stored value cannot be reversed.
|
||||
*/
|
||||
public static final class FixedCredentialStore {
|
||||
private final Map<String, byte[]> salts = new HashMap<>();
|
||||
private final Map<String, byte[]> hashes = new HashMap<>();
|
||||
|
||||
/** Store password as SHA-256(salt || password). */
|
||||
public void storePassword(String username, String password) {
|
||||
byte[] salt = generateSalt();
|
||||
byte[] hash = sha256(password, salt);
|
||||
salts.put(username, salt);
|
||||
hashes.put(username, hash);
|
||||
}
|
||||
|
||||
/** Verify by hashing candidate with stored salt and comparing. */
|
||||
public boolean verify(String username, String password) {
|
||||
byte[] salt = salts.get(username);
|
||||
byte[] hash = hashes.get(username);
|
||||
if (salt == null || hash == null) return false;
|
||||
return Arrays.equals(sha256(password, salt), hash);
|
||||
}
|
||||
|
||||
/** Exposes raw stored hash (what a DB dump would show — unrecoverable). */
|
||||
public byte[] rawHash(String username) {
|
||||
return hashes.get(username);
|
||||
}
|
||||
|
||||
/** Exposes raw salt (what a DB dump would show). */
|
||||
public byte[] rawSalt(String username) {
|
||||
return salts.get(username);
|
||||
}
|
||||
|
||||
// FIX: no extractPassword() method. DB access gives hash+salt, not plaintext.
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue