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
121 lines
5.4 KiB
Java
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");
|
|
}
|
|
}
|