java-topology/defects/invoiceninja-0002/test/InvoiceNinja0002Test.java
russell@unturf.com 0f13a71cb4 invoiceninja: 3 defects (MOAD-0001/0003/0004); onlyoffice: 1 defect (MOAD-0001); all 5 MOADs scanned
invoiceninja-0001: S3Cleanup in_array O(D*C) across 3 DB shards, fix: array_flip+isset O(D+C), 9499x at D=C=10000
invoiceninja-0002: CheckoutCom webhook logs $request->all() on HMAC mismatch (CWE-312), exposes card fingerprints/BIN
invoiceninja-0003: 20 Mailable build() methods missing App::forgetInstance('translator') before setLocale, Octane locale leak (MOAD-0003)
onlyoffice-0001: Table.js 8x Array.indexOf inside for(curRow) loops O(R*A), fix: new Set+has O(R+A), 375x at R=1000

4/4 unit tests PASS
2026-03-31 20:53:13 -04:00

121 lines
5.4 KiB
Java

package unit;
import java.util.*;
import java.util.regex.*;
/**
* InvoiceNinja0002Test -- CWE-312 test for invoiceninja-0002.
*
* invoiceninja-0002: CheckoutComPaymentDriver webhook logs payment body (CWE-312)
* processWebhookRequest() verifies a cko-signature HMAC. On mismatch it calls:
* nlog("Hash Mismatch = " + $request->header('cko-signature') + " " + computedHmac)
* nlog($request->all())
*
* The Checkout.com webhook body contains payment source data including card
* fingerprints, bin codes, card scheme, issuer country, and payment method IDs
* (see CheckoutWebhook.php line 71 -- the full example payload). Logging
* $request->all() on auth failure writes this to nlog / Laravel log files.
* The signature header itself is also logged, which leaks the attacker-supplied
* value that could be used for replay analysis.
*
* Fix: replace the two nlog() calls with a single sanitized message that
* records only that a mismatch occurred plus a truncated prefix of the
* company_key for traceability. No request body, no signature value.
*
* File: app/PaymentDrivers/CheckoutComPaymentDriver.php
* Method: processWebhookRequest()
* Lines: 575-576
*/
public class InvoiceNinja0002Test {
// Simulate the defective log output
static String defectiveLogOutput(Map<String, Object> requestBody, String signatureHeader, String computedHmac) {
StringBuilder sb = new StringBuilder();
sb.append("Hash Mismatch = ").append(signatureHeader).append(" ").append(computedHmac).append("\n");
sb.append(requestBody.toString());
return sb.toString();
}
// Simulate the fixed log output
static String fixedLogOutput(String companyKeyPrefix) {
return "CheckoutCom webhook signature mismatch for company_key=" + companyKeyPrefix + "...\n";
}
// Check that a string does not contain sensitive payment data
static void assertNoSensitiveData(String logOutput, String label) {
String[] sensitivePatterns = {
"fingerprint",
"card_type",
"bin",
"scheme",
"last_4",
"payment_method",
"cko-signature",
"source.*id",
"captures",
"void",
};
for (String pattern : sensitivePatterns) {
if (logOutput.toLowerCase().contains(pattern.toLowerCase().replace(".*", ""))) {
throw new AssertionError(label + ": log output contains sensitive pattern '" + pattern + "'");
}
}
}
public static void main(String[] args) {
System.out.println("invoiceninja-0002: CheckoutCom webhook CWE-312 credential leak in logs");
System.out.println();
// Simulate a realistic Checkout.com webhook payload (card fingerprint, bin, scheme, etc.)
Map<String, Object> webhookBody = new LinkedHashMap<>();
webhookBody.put("id", "pay_oqwbsd22kvpuvd35y5fhbdawxa");
webhookBody.put("source.id", "src_ghavmefpetjellmteqwj5jjcli");
webhookBody.put("source.fingerprint", "BD864B08D0B098DD83052A038FD2BA967DF2D48E375AAEEF54E37BC36B385E9A");
webhookBody.put("source.bin", "424242");
webhookBody.put("source.last_4", "4242");
webhookBody.put("source.scheme", "VISA");
webhookBody.put("source.card_type", "CREDIT");
webhookBody.put("payment_method", "card");
String suppliedSig = "abc123forgedsignature";
String computedHmac = "def456computedhmac";
String companyKey = "xyz789secretcompanykey";
// --- Defective path ---
String defectiveOutput = defectiveLogOutput(webhookBody, suppliedSig, computedHmac);
System.out.println("Defective log output (first 120 chars): " +
defectiveOutput.substring(0, Math.min(120, defectiveOutput.length())));
System.out.println();
// Verify defective output DOES contain sensitive data
boolean foundSensitive = defectiveOutput.contains("fingerprint") ||
defectiveOutput.contains("424242") ||
defectiveOutput.contains("VISA") ||
defectiveOutput.contains(suppliedSig);
if (!foundSensitive) {
throw new AssertionError("Test setup error: defective output should contain sensitive data");
}
System.out.println(" VERIFIED: defective path exposes card fingerprint, bin, scheme, signature header");
// --- Fixed path ---
String fixedOutput = fixedLogOutput(companyKey.substring(0, 6));
System.out.println("Fixed log output: " + fixedOutput.trim());
System.out.println();
// Verify fixed output does NOT contain sensitive data
assertNoSensitiveData(fixedOutput, "fixed");
System.out.println(" VERIFIED: fixed path contains no card data, no signature value");
// Verify fixed output still contains useful diagnostic info
if (!fixedOutput.contains("mismatch") && !fixedOutput.contains("Mismatch")) {
throw new AssertionError("Fixed output should mention mismatch for observability");
}
if (!fixedOutput.contains(companyKey.substring(0, 6))) {
throw new AssertionError("Fixed output should contain truncated company_key prefix for traceability");
}
System.out.println(" VERIFIED: fixed path retains mismatch event + truncated company_key for traceability");
System.out.println();
System.out.println("PASS");
}
}