Skip to content

Security: Fix CWE-79 (Cross-Site Scripting) vulnerability in src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java:64#665

Open
appsecai-app[bot] wants to merge 1 commit intomainfrom
appsecai/fix-group/69c734e2-e9c6fa3e-9ef
Open

Security: Fix CWE-79 (Cross-Site Scripting) vulnerability in src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java:64#665
appsecai-app[bot] wants to merge 1 commit intomainfrom
appsecai/fix-group/69c734e2-e9c6fa3e-9ef

Conversation

@appsecai-app
Copy link
Copy Markdown

@appsecai-app appsecai-app bot commented Mar 28, 2026

What we found

  • AppSecAI Vulnerability ID: 69c734eb
  • Vulnerability: CWE-79: Cross-site Scripting (XSS)
  • Severity: Medium
  • File: src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java:64
  • Detected By: OpenGrep
  • Detection Rule: No Direct Response Writer

Description: Detected user-controlled input flowing into a response OutputStream without HTML encoding. This bypasses view-layer escaping and exposes the application to reflected cross-site scripting (XSS) vulnerabilities.

Why this matters

Risk if not fixed: An attacker could inject malicious JavaScript that executes in victims' browsers, potentially stealing session cookies, redirecting users to phishing sites, or performing actions on behalf of authenticated users.

Attack vector: An attacker crafts a URL with a malicious parameter name (e.g., ?<script>alert(1)</script>=BenchmarkTest02316) and tricks a user into clicking it. The parameter name is extracted, passed through the application unchanged, and written directly into the HTML response body without encoding.

Risk level: Medium to High — Depends on sensitivity of data accessible through the victim's session.

Why we're changing it

Data Flow Analysis

Source (lines 45–58): The servlet iterates request.getParameterNames(). When a parameter's value equals "BenchmarkTest02316", its name is assigned to param. HTTP parameter names are fully attacker-controlled — an attacker submits ?<script>alert(1)</script>=BenchmarkTest02316.

Propagation (lines 60, 70–74): doSomething() passes param to thing.doSomething(param):

  • Thing1.doSomething: return i — identity, no transformation.
  • Thing2.doSomething: return new StringBuilder(i).toString() — identity, no transformation.

Neither implementation applies HTML encoding or any sanitization.

Sink (line 64): response.getWriter().format(bar, obj)bar (the attacker-controlled parameter name) serves as the format template passed directly to PrintWriter.format(). The response content-type is text/html;charset=UTF-8 (line 41). The X-XSS-Protection: 0 header is explicitly set on line 62, disabling browser-side mitigation.

The attacker payload <script>alert(1)</script> as a parameter name passes through Thing1/Thing2 unchanged and is written verbatim into the HTML response body via format().

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
    A["HTTP Request Parameter Name<br/>(Attacker-Controlled)"] --> B["param = request.getParameterName()"] 
    B --> C["bar = doSomething(param)<br/>(Thing1/Thing2 - No Encoding)"]
    C --> D["response.getWriter().format(bar, obj)<br/>(Unescaped HTML Sink)"]
    D --> E["❌ XSS Payload Executed<br/>in Victim Browser"]
    
    F["✅ Fix - Encode.forHtml(bar)"] -.-> G["HTML Special Chars Encoded<br/>((, ),  and , etc.)"]
    G -.-> H["🛡️ Payload Rendered as Text<br/>Attack Blocked"]
    
    style A fill:#EDE9FE,stroke:#7C3AED
    style B fill:#EDE9FE,stroke:#7C3AED
    style C fill:#FFE5E5,stroke:#F65A5A
    style D fill:#FFE5E5,stroke:#F65A5A
    style E fill:#FEF3C7,stroke:#F59E0B
    style F fill:#DCFCE7,stroke:#16A34A
    style G fill:#DCFCE7,stroke:#16A34A
    style H fill:#DCFCE7,stroke:#16A34A
Loading

How we confirmed

Manual Verification Steps

  1. Trace the parameter source: Confirm that request.getParameterNames() returns attacker-controlled names from the HTTP request.
  2. Verify no encoding in propagation: Check that Thing1.doSomething() and Thing2.doSomething() perform identity operations without HTML encoding.
  3. Identify the sink: Confirm that response.getWriter().format(bar, obj) writes bar directly into an HTML response without encoding.
  4. Check response context: Verify that the response content-type is text/html and that X-XSS-Protection: 0 is set, confirming the response is rendered as HTML in browsers.
  5. Test with payload: Submit a request with a parameter name containing HTML/JavaScript (e.g., ?<img src=x onerror=alert(1)>=BenchmarkTest02316) and confirm the script executes in the response.
Runnable Verification Script (click to expand)

Save this script and run with bash verify_fix.sh:

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

echo "=== Verification: CWE-79 Cross-Site Scripting Fix ==="

# Step 1: Check that the vulnerable format() call has been replaced
echo "Step 1: Verifying format() call has been replaced with encoded write()..."
if grep -n "response.getWriter().format(bar" src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java; then
    echo "❌ FAILED: Vulnerable format() call still present"
    exit 1
fi
echo "✅ PASSED: format() call removed"

# Step 2: Check that Encode.forHtml() is being used
echo ""
echo "Step 2: Verifying Encode.forHtml() is applied to bar..."
if grep -n "Encode.forHtml(bar)" src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java; then
    echo "✅ PASSED: Encode.forHtml() found"
else
    echo "❌ FAILED: Encode.forHtml() not found"
    exit 1
fi

# Step 3: Check that OWASP Encoder import is present
echo ""
echo "Step 3: Verifying OWASP Encoder import..."
if grep -n "import org.owasp.encoder.Encode" src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java; then
    echo "✅ PASSED: OWASP Encoder import present"
else
    echo "❌ FAILED: OWASP Encoder import missing"
    exit 1
fi

# Step 4: Verify response.getWriter().write() is used instead of format()
echo ""
echo "Step 4: Verifying response.getWriter().write() is used..."
if grep -n "response.getWriter().write(" src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java; then
    echo "✅ PASSED: write() method used for output"
else
    echo "❌ FAILED: write() method not found"
    exit 1
fi

# Step 5: Compile the fixed code to ensure no syntax errors
echo ""
echo "Step 5: Compiling fixed code..."
if mvn -q compile -DskipTests 2>/dev/null; then
    echo "✅ PASSED: Code compiles successfully"
else
    echo "❌ FAILED: Compilation errors detected"
    exit 1
fi

echo ""
echo "=== All verification checks passed ==="
exit 0

Vulnerable flow: src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java:64

Cross-Site Scripting

%%{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"]
        direction LR
        A1["Project"] --> A2["Unescaped user input in HTML output"]
        A2 --> A3["💥 XSS Script Executed"]
    end

    Vulnerable ~~~ Fixed

    subgraph Fixed["✅ Fixed Flow"]
        direction LR
        B1["Project"] --> B2["Context-aware output encoding"]
        B3["🛡️ Attack Blocked"]
        B2 --> B3
    end

    style A2 fill:#FFE5E5,color:#000
    style A3 fill:#ffa94d,color:#000
    style B2 fill:#74c0fc,color:#000
    style B3 fill:#DCFCE7,color:#000
Loading

How we fixed it

Fix Description

Root cause: User-controlled input flows from request parameters through ThingFactory.doSomething() into the 'bar' variable, which is then passed directly as the format string argument to response.getWriter().format(). This bypasses any view-layer HTML escaping, allowing an attacker to inject arbitrary HTML and JavaScript into the HTTP response, resulting in reflected XSS.

Fix approach: The fix replaces the unescaped format() call with response.getWriter().write() wrapped by org.owasp.encoder.Encode.forHtml(), which contextually encodes HTML special characters (<, >, &, ", ') into their safe entity equivalents. This ensures that even if 'bar' contains attacker-supplied markup, it is rendered as literal text rather than executable HTML/JS. The OWASP Java Encoder library is the established, context-aware encoding solution for this pattern.

Alternatives considered:

  • String.replace() manual escaping of <, >, &, etc. — rejected because manual character replacement is error-prone and misses edge cases like single quotes, backticks, and Unicode variants
  • Apache Commons Text StringEscapeUtils.escapeHtml4() — functionally equivalent but OWASP Java Encoder is purpose-built for contextual output encoding and already used in the OWASP Benchmark project
  • Keeping format() with encoded bar — rejected because format() interprets % sequences in bar as format specifiers, creating a secondary format-string injection vector even after HTML encoding

Code Changes

Before:

response.getWriter().format(bar, obj);

After:

response.getWriter().write(Encode.forHtml(bar));

Import added:

import org.owasp.encoder.Encode;

Vulnerabilities Addressed

  • Grouped findings in scope: 1
  • Findings fixed in this PR: 1
  • Primary CWE family: CWE-79
  • Files covered: src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java
# Finding Detection Severity Location Status
1 Cross-Site Scripting
CWE-79
OpenGrep
No Direct Response Writer
Medium src/main/java/org/owasp/benchmark/testcode/BenchmarkTest02316.java:64 Fixed

How we validated it

  • Verified that the vulnerable format() call has been replaced with write() wrapped by Encode.forHtml()
  • Confirmed that the OWASP Encoder library is imported and available in the project dependencies
  • Compiled the fixed code to ensure no syntax errors or type mismatches
  • Traced the data flow to confirm that all attacker-controlled input reaching the HTML sink is now HTML-encoded
  • Verified that the encoding is context-appropriate (HTML context, not JavaScript or URL context)

How to verify

Reviewers can verify this fix by:

  1. Visual inspection: Check that line 64 now uses Encode.forHtml(bar) instead of format(bar, obj)
  2. Dependency check: Confirm that owasp-java-encoder is in the project's pom.xml or build.gradle
  3. Compile test: Run mvn clean compile to ensure the fix compiles without errors
  4. Manual testing: Deploy the fixed code and submit a request with a parameter name containing HTML/JavaScript (e.g., ?<img src=x onerror=alert(1)>=BenchmarkTest02316). The payload should be rendered as literal text in the response, not executed.
  5. Automated testing: Run the project's security test suite to confirm that XSS payloads are now blocked

Before you merge

  • Fix addresses the root cause (unescaped user input in HTML sink), not just the symptom
  • No new security vulnerabilities introduced (e.g., double-encoding, format-string injection)
  • Code follows project conventions and style guidelines
  • Edge cases handled (null input, empty strings, special characters, Unicode)
  • No functionality regression (output still renders correctly, just safely)
  • Output encoding applied to all contexts (HTML context confirmed; verify no other sinks exist)
  • OWASP Encoder dependency is available and version-pinned

Learn more


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

Replace unescaped response.getWriter().format() with HTML-encoded
Encode.forHtml() to prevent reflected XSS attacks via HTTP parameter
names. Fixes 1 CWE-79 vulnerability detected by OpenGrep.
@kevinfealey kevinfealey added the 1.0.3 Version 1.0.3 label Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

1.0.3 Version 1.0.3

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants