Skip to content

[Reachable] Fix CWE-328 (Weak Hash Algorithm) vulnerability in src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java:53#732

Open
appsecai-app[bot] wants to merge 1 commit into
mainfrom
appsecai/fix-group/6a4286de-644cc057-f87
Open

[Reachable] Fix CWE-328 (Weak Hash Algorithm) vulnerability in src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java:53#732
appsecai-app[bot] wants to merge 1 commit into
mainfrom
appsecai/fix-group/6a4286de-644cc057-f87

Conversation

@appsecai-app

@appsecai-app appsecai-app Bot commented Jun 29, 2026

Copy link
Copy Markdown

What we found

  • AppSecAI Vulnerability ID: 6a428858
  • Vulnerability: CWE-328: Weak Hash
  • Severity: Medium
  • File: src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java:53
  • Detected By: OpenGrep
  • Detection Rule: Use Of Md5

Description: Detected MD5 hash algorithm which is considered insecure. MD5 is not collision resistant and is therefore not suitable as a cryptographic signature. Use HMAC instead.

Vulnerable Code Path

  • Line 53: java.security.MessageDigest.getInstance("MD5") — hardcoded weak hash algorithm
  • Lines 43–50: User input from request.getParameterValues("BenchmarkTest00708") flows through ThingFactory.createThing().doSomething(param) into bar, which is the data hashed
  • Lines 71–81: Hash result written to passwordFile.txt under Utils.TESTFILES_DIR — security-sensitive storage context
  • Lines 82–90: Response confirms 'Sensitive value ... hashed and stored' — cryptographic intent is data protection, not a non-security identifier
  • No suppression comments: No nosemgrep, noqa, or safe: suppression comments present in the file

Why this matters

Risk if not fixed: A bare unkeyed digest over attacker-controlled user input remains a CWE-328 weak-hash finding regardless of algorithm strength. The output is entirely predictable from the input, providing no integrity guarantee against a motivated attacker who can pre-compute any desired hash value. An adversary can forge hash values, bypass integrity checks, or replay crafted inputs with known hashes.

Risk level: Medium — Should be addressed in regular security maintenance. Exploitation requires control of the servlet input (via HTTP request parameters), but the servlet is publicly accessible.

Context: This servlet hashes arbitrary user-supplied data for logging/benchmark purposes. The filename passwordFile.txt and response message confirm the developer intent is security-sensitive data protection, not a non-security fingerprint.

Why we're changing it

Status: Confirmed vulnerability requiring remediation.

Finding: Line 53 uses java.security.MessageDigest.getInstance("MD5") — a bare (unkeyed) weak hash algorithm. Even if the algorithm were upgraded to SHA-256, a bare MessageDigest over attacker-controlled user input still constitutes a CWE-328 weak-hash finding because the digest output is entirely determined by the attacker's input with no secret component. The scanner explicitly requires HMAC as the remediation.

Recommended Remediation: Replace MessageDigest.getInstance("MD5") with a keyed-integrity primitive. For arbitrary user-supplied data (not passwords), use HMAC-SHA256 (Mac.getInstance("HmacSHA256")) with a server-side secret key. This binds the hash output to a secret that the attacker cannot control, preventing pre-computation and forgery.

How we confirmed

Automated checks we ran:

  • Static validation (syntax, semantic, import, security): passed.
  • Codebase-wide grep confirmed no other callers of MessageDigest.getInstance("MD5") in this file.

LLM review of the diff:

  • Functional review: no issues identified.
  • Code quality review: no issues identified.
  • Security review: no issues identified.

Vulnerability Flow Diagram

%%{init: {'theme':'base','themeVariables':{'fontFamily':'ui-sans-serif, Inter, system-ui, sans-serif','primaryColor':'#EDE9FE','primaryTextColor':'#1A1A2E','primaryBorderColor':'#7C3AED','lineColor':'#5B21B6','secondaryColor':'#FEF3C7','tertiaryColor':'#DCFCE7'}}}%%
flowchart TD
    subgraph Vulnerable["❌ Vulnerable Flow - CWE-328 Weak Hash"]
        A1["Attacker Input<br/>BenchmarkTest00708 param"] --> A2["doPost in<br/>BenchmarkTest00708.java"]
        A2 --> A3["MessageDigest.getInstance<br/>Line 53 - MD5 Algorithm"]
        A3 --> A4["md.update + digest<br/>Unkeyed bare digest"]
        A4 --> A5["💥 Attacker Pre-computes<br/>Any desired MD5 hash"]
        A5 --> A6["💥 Hash stored to<br/>passwordFile.txt - Forged"]
    end

    subgraph Fixed["✅ Fixed Flow - HmacSHA256"]
        B1["User Input<br/>BenchmarkTest00708 param"] --> B2["doPost in<br/>BenchmarkTest00708.java"]
        B2 --> B3["HMAC_SIGNING_KEY loaded<br/>from environment variable"]
        B3 --> B4{"Key present?"}
        B4 -->|"Missing or empty"| B5["🛡️ Fail Closed<br/>GeneralSecurityException"]
        B4 -->|"Key available"| B6["Mac.getInstance<br/>HmacSHA256 with secret key"]
        B6 --> B7["mac.init + update + doFinal<br/>Keyed HMAC output"]
        B7 --> B8["🛡️ Attacker Cannot Forge<br/>Hash bound to secret key"]
    end

    style A3 fill:#FFE5E5,stroke:#F65A5A,color:#000
    style A5 fill:#FEF3C7,stroke:#F59E0B,color:#000
    style A6 fill:#FEF3C7,stroke:#F59E0B,color:#000
    style B3 fill:#EDE9FE,stroke:#7C3AED,color:#000
    style B5 fill:#DCFCE7,stroke:#16A34A,color:#000
    style B6 fill:#EDE9FE,stroke:#7C3AED,color:#000
    style B8 fill:#DCFCE7,stroke:#16A34A,color:#000
Loading

Vulnerable flow: src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java:53

Weak Hash Algorithm

%%{init: {'theme':'base','themeVariables':{'fontFamily':'ui-sans-serif, Inter, system-ui, sans-serif','primaryColor':'#EDE9FE','primaryTextColor':'#1A1A2E','primaryBorderColor':'#7C3AED','lineColor':'#5B21B6','secondaryColor':'#FEF3C7','tertiaryColor':'#DCFCE7'}}}%%
flowchart TD
    subgraph Vulnerable["❌ Vulnerable Flow - CWE-328 Weak Hash"]
        direction LR
        A1["Attacker Input<br/>BenchmarkTest00708 param"] --> A2["doPost in<br/>BenchmarkTest00708.java"]
        A2 --> A3["MessageDigest.getInstance<br/>Line 53 - MD5 Algorithm"]
        A3 --> A4["md.update(input)<br/>Unkeyed bare digest"]
        A4 --> A5["💥 Attacker Pre-computes<br/>Any desired MD5 hash"]
        A5 --> A6["💥 Hash stored to<br/>passwordFile.txt - Forged"]
    end

    Vulnerable ~~~ Fixed

    subgraph Fixed["✅ Fixed Flow - HmacSHA256"]
        direction LR
        B1["User Input<br/>BenchmarkTest00708 param"] --> B2["doPost in<br/>BenchmarkTest00708.java"]
        B2 --> B3["HMAC_SIGNING_KEY loaded<br/>from environment variable"]
        B3 --> B4{"Key present?"}
        B4 -->|"Missing or empty"| B5["🛡️ Fail Closed<br/>GeneralSecurityException"]
        B4 -->|"Key available"| B6["Mac.getInstance<br/>HmacSHA256 with secret key"]
        B6 --> B7["mac.update + doFinal<br/>Keyed HMAC output"]
        B7 --> B8["🛡️ Attacker Cannot Forge<br/>Hash bound to secret key"]
    end

    style A3 fill:#FFE5E5,color:#1A1A2E
    style A5 fill:#ffa94d,color:#000
    style A6 fill:#ffa94d,color:#000
    style B3 fill:#74c0fc,color:#000
    style B5 fill:#DCFCE7,color:#000
    style B6 fill:#74c0fc,color:#000
    style B8 fill:#DCFCE7,color:#000
Loading

How we fixed it

Root Cause

The servlet used java.security.MessageDigest with a bare (unkeyed) hash algorithm. Even after swapping MD5 for SHA-256, a bare MessageDigest over attacker-controlled user input remains a CWE-328 weak-hash finding because the digest output is entirely determined by the attacker's input — there is no secret component. The scanner explicitly requires HMAC as the remediation.

Fix Approach

The MessageDigest path is replaced entirely with javax.crypto.Mac using HmacSHA256. The HMAC key is loaded at runtime from the HMAC_SIGNING_KEY environment variable. Because the key is not attacker-controlled, an adversary cannot pre-compute or forge the resulting digest regardless of what input they supply. The catch clause is widened to GeneralSecurityException to cover both InvalidKeyException (from mac.init) and NoSuchAlgorithmException (from Mac.getInstance) under a single handler, preserving the existing error-handling structure.

Alternatives Considered

  • SHA-256 bare digest (attempted in prior iteration) — rejected because a bare digest over user input still leaves the output fully attacker-determined; the scanner and description explicitly require a keyed MAC.
  • PBKDF2 / bcrypt — applicable only when hashing passwords for storage; this servlet hashes arbitrary user-supplied data for integrity/logging purposes, so HMAC-SHA256 is the appropriate keyed-integrity primitive.
  • Hardcoded HMAC key — rejected per security guidelines; keys must come from a managed secret (environment variable or secret store), never from source code.

Code Changes

The fix modifies src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java:

Before:

MessageDigest md = MessageDigest.getInstance("MD5");
md.update(bar.getBytes(StandardCharsets.UTF_8));
byte[] digest = md.digest();

After:

String hmacKey = System.getenv("HMAC_SIGNING_KEY");
if (hmacKey == null || hmacKey.isEmpty()) {
    throw new GeneralSecurityException("HMAC_SIGNING_KEY environment variable not set");
}
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(hmacKey.getBytes(StandardCharsets.UTF_8), 0, hmacKey.getBytes(StandardCharsets.UTF_8).length, "HmacSHA256"));
mac.update(bar.getBytes(StandardCharsets.UTF_8));
byte[] digest = mac.doFinal();

The catch clause is updated from NoSuchAlgorithmException to GeneralSecurityException to handle both InvalidKeyException and NoSuchAlgorithmException.

Files changed in this pull request (derived from the generated diff):

  • src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java

Vulnerabilities Addressed

  • Grouped findings in scope: 1
  • Findings fixed in this PR: 1
  • Primary CWE family: CWE-328
  • Files covered: src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java
# Finding Detection Severity Location Status
1 Weak Hash Algorithm
CWE-328
OpenGrep
Use Of Md5
Medium src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java:53 Fixed

How we checked this fix

Automated checks we ran:

  • Static validation (syntax, semantic, import, security): passed.
  • Import verification: javax.crypto.Mac and javax.crypto.spec.SecretKeySpec are available in Java 8+.

LLM review of the diff:

  • Functional review: HMAC initialization and update/doFinal sequence is correct; key is loaded from environment at runtime; fail-closed behavior on missing key is correct.
  • Code quality review: no issues identified.
  • Security review: no issues identified.

Verification of fix completeness:

  • Codebase-wide grep confirms no other callers of MessageDigest.getInstance("MD5") in this file.
  • The vulnerable sink at line 53 is the only MD5 usage in BenchmarkTest00708.java.

Reachability analysis

Reachability: Reachable

At least one remediated finding has a traced attacker-controlled path to the vulnerable sink.

Status: Reachable (confidence 0.97, source: grounded call graph via Joern)

Exploitability score: 75/100

Rationale: The servlet endpoint is publicly accessible via HTTP POST. User input from request.getParameterValues("BenchmarkTest00708") flows through ThingFactory.createThing().doSomething(param) directly into the hash sink at line 53. The grounded call graph confirms the entry point (doPost) reaches the vulnerable operation without guard conditions. An attacker can supply arbitrary input via the HTTP parameter and observe the resulting hash value written to passwordFile.txt or echoed in the response, enabling pre-computation and forgery attacks.

Attack prerequisites:

  • Network access to the servlet (HTTP POST to the benchmark endpoint)
  • Ability to control the BenchmarkTest00708 request parameter
  • Read access to passwordFile.txt or ability to observe hash values in responses

Confidence breakdown:

  • Decision confidence: High (0.97) — structural vulnerability confirmed by direct code inspection and grounded call graph.
  • Evidence quality: Strong — MD5 usage is explicit and hardcoded; data flow from user input to hash sink is direct and unconditional.
  • Ensemble agreement: Single tool (OpenGrep) flagged this finding; no conflicting signals.
  • Reachability tier: Tier 1 (direct, unconditional path from public entry point to sink).

How to verify

Manual Verification Steps

  1. Inspect the fix in the diff:

    • Confirm line 53 no longer contains MessageDigest.getInstance("MD5").
    • Confirm Mac.getInstance("HmacSHA256") is used instead.
    • Confirm the HMAC key is loaded from the HMAC_SIGNING_KEY environment variable.
    • Confirm the catch clause handles GeneralSecurityException.
  2. Verify environment variable contract:

    • Confirm the placeholder is documented as required and cryptographically random.
  3. Verify imports:

    • Confirm javax.crypto.Mac and javax.crypto.spec.SecretKeySpec are imported.
    • Confirm java.nio.charset.StandardCharsets is imported (used for UTF-8 encoding).
  4. Compile and run unit tests:

    • Build the project: mvn clean compile.
    • Run tests: mvn test.
    • Confirm no compilation errors and no test regressions.
  5. Local runtime test (optional):

    • Set the environment variable: export HMAC_SIGNING_KEY=$(openssl rand -base64 32)
    • Deploy the application locally.
    • Send a POST request to the servlet with a test parameter.
    • Confirm the response indicates successful hashing (no HTTP 500 error).
    • Confirm passwordFile.txt is created/updated with hash output.
Runnable Verification Script (click to expand)

Save this script and run with bash verify_fix.sh:

#!/bin/bash
# Verification script for CWE-328 fix in BenchmarkTest00708.java
set -e

echo "=== Verification: CWE-328 Weak Hash Fix ==="

# Step 1: Verify MD5 is removed
echo "Step 1: Checking that MD5 is removed from line 53..."
if grep -n 'MessageDigest.getInstance("MD5")' src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java; then
    echo "FAIL: MD5 still present in BenchmarkTest00708.java"
    exit 1
fi
echo "PASS: MD5 removed"

# Step 2: Verify HmacSHA256 is present
echo "Step 2: Checking that HmacSHA256 is used..."
if ! grep -n 'Mac.getInstance("HmacSHA256")' src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java; then
    echo "FAIL: HmacSHA256 not found in BenchmarkTest00708.java"
    exit 1
fi
echo "PASS: HmacSHA256 found"

# Step 3: Verify environment variable is read
echo "Step 3: Checking that HMAC_SIGNING_KEY environment variable is read..."
if ! grep -n 'System.getenv("HMAC_SIGNING_KEY")' src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java; then
    echo "FAIL: HMAC_SIGNING_KEY environment variable not read"
    exit 1
fi
echo "PASS: HMAC_SIGNING_KEY environment variable is read"

# Step 4: Verify SecretKeySpec is imported
echo "Step 4: Checking that SecretKeySpec is imported..."
if ! grep -n 'import javax.crypto.spec.SecretKeySpec' src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java; then
    echo "FAIL: SecretKeySpec import not found"
    exit 1
fi
echo "PASS: SecretKeySpec imported"

# Step 5: Verify Mac is imported
echo "Step 5: Checking that Mac is imported..."
if ! grep -n 'import javax.crypto.Mac' src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java; then
    echo "FAIL: Mac import not found"
    exit 1
fi
echo "PASS: Mac imported"

# Step 6: Verify catch clause handles GeneralSecurityException
echo "Step 6: Checking that catch clause handles GeneralSecurityException..."
if ! grep -n 'catch.*GeneralSecurityException' src/main/java/org/owasp/benchmark/testcode/BenchmarkTest00708.java; then
    echo "FAIL: GeneralSecurityException catch clause not found"
    exit 1
fi
echo "PASS: GeneralSecurityException catch clause found"

# Step 7: Compile the project
echo "Step 7: Compiling the project..."
if ! mvn clean compile -q; then
    echo "FAIL: Project compilation failed"
    exit 1
fi
echo "PASS: Project compiled successfully"

echo ""
echo "=== All verification checks passed ==="

Before you merge

  • Review ## How we fixed it: Confirm the fix addresses the root cause (unkeyed digest → keyed HMAC) and not just the symptom (MD5 → SHA-256).
  • Review ## How we checked this fix: Confirm the automated checks and LLM review are sufficient for your risk tolerance.
  • Review ## Reachability analysis: Confirm the attacker path (public HTTP endpoint → user parameter → hash sink) matches your understanding of the codebase.
  • Provision HMAC_SIGNING_KEY in all environments before merging:
    • Generate a cryptographically random value: openssl rand -base64 32
    • Store in your secret manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, etc.) and inject as an environment variable at container/process startup.
    • REQUIRED before merge: The application will throw GeneralSecurityException and return HTTP 500 for all hash requests until this variable is set.
    HMAC_SIGNING_KEY=   # Required: cryptographically random secret for HMAC-SHA256 signing
    
  • No data migration needed: The file written by this servlet (passwordFile.txt) stores hash values for logging/benchmark purposes only; existing entries written with the old algorithm remain in the file but are not read back by this code path.
  • Existing password hashes: This servlet does not verify stored hashes; it only writes new ones. No rehash-on-login or password reset is required.
  • Follow ## How to verify in your environment before merging.
  • Review the diff for project-specific regression risk.

Learn more


This fix was generated by AppSecAI. Please review before merging.

@kevinfealey kevinfealey added the 1.0.9 Release 1.0.9 label Jun 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.0.9 Release 1.0.9

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants