Vulnerability GHSA-ghc9-756g-vrcv
Summary
JSON-java: JSONTokener Has Unbounded Numeric String Resource-Exhaustion DoS
Details
1. Executive Summary
JSON-java version 20260522 contains an unguarded path from an HTTP request body to unbounded BigInteger and BigDecimal construction. JSONTokener.nextSimpleValue() accumulates numeric characters into a StringBuilder with no length cap, then passes the full string to JSONObject.stringToNumber(), which calls new BigInteger(val) or new BigDecimal(val) directly. Both constructors have polynomial time complexity (O(n^1.585) for BigInteger).
A 488 KB JSON body — well within nginx's default 1 MB body-size limit — contains a 500,000-digit numeric literal that blocks the handling thread for approximately 3 seconds. Spring Boot has no default size limit for application/json request bodies. No parser configuration option (withStrictMode, withKeepStrings) prevents this. All three primary entry points are affected: new JSONObject(String), new JSONArray(String), and XML.toJSONObject(String).
This vulnerability is distinct from CVE-2023-5072 (which addressed StackOverflowError from deeply nested JSON structures via a try/catch in nextValue()) and from CVE-2022-45688 (which addressed recursive XML nesting depth). The numeric string length issue has been present since BigInteger support was introduced in release 20150729 and has never been fixed in any subsequent release.
Attack Complexity is rated High because successful exploitation requires the target application to be using org.json specifically (rather than the default Jackson library used by Spring Boot), and requires the developer to have explicitly passed user-controlled input to new JSONObject() or equivalent. These are deployment conditions beyond the attacker's control. See Section 6 for the full CVSS rationale and Section 8 for the comparison with Jackson's treatment of the same issue class.
This vulnerability was reproduced locally against json-20260522.jar with full evidence provided below.
3. Affected Code Locations
| File | Lines | Description |
|---|---|---|
JSONTokener.java |
500–504 | nextSimpleValue() — unbounded character accumulation into StringBuilder, no length cap |
JSONObject.java |
2721–2748 | stringToNumber() — new BigInteger(val) / new BigDecimal(val) called directly with no length guard |
5. Attack Scenarios
5.1 Thread-Pool Exhaustion via Concurrent Requests
A Java web application that explicitly uses org.json for request body parsing exposes a JSON endpoint. The developer parses the request body with new JSONObject(requestBody).
Single attack request:
POST /api/order HTTP/1.1
Host: target.example.com
Content-Type: application/json
Content-Length: 500011
{"amount":999...999} (500,000 nines — ~488 KB body)
Effect: This single request occupies one Tomcat thread for ~3 seconds. To exhaust a default 200-thread Tomcat pool, the attacker sends 200 concurrent such requests — a total of ~95 MB of concurrent traffic (200 × 488 KB), achievable with any standard load-generation tool (ab, wrk, hey). While those threads are occupied, legitimate user requests queue and time out.
Why common defaults do not prevent this:
- nginx default
client_max_body_size= 1 MB → 488 KB payload passes through - Spring Boot has no default size limit for
application/json@RequestBody(Tomcat'smaxPostSizeapplies only toapplication/x-www-form-urlencodedandmultipart/form-data, not to JSON) - No configuration option within org.json itself provides any protection
5.2 Amplification Ratio
The blocking time grows super-linearly with payload size, creating an asymmetric attack:
| Payload size | Thread blockage | Ratio (ms per KB sent) |
|---|---|---|
| 9 KB (10K digits) | 17 ms | 1.9 ms/KB |
| 97 KB (100K digits) | 123 ms | 1.3 ms/KB |
| 195 KB (200K digits) | 479 ms | 2.5 ms/KB |
| 488 KB (500K digits) | ~2,975 ms | 6.1 ms/KB |
| 976 KB (1M digits) | ~11,909 ms | 12.2 ms/KB |
A 488 KB request — well within nginx's 1 MB default — ties up a server thread for 3 seconds. The ratio of CPU time consumed to bytes sent grows with payload size due to Karatsuba's super-linear complexity.
5.3 All Primary Entry Points Are Affected
The vulnerability is not limited to new JSONObject(String). Any org.json parsing call on the same numeric input produces the same delay:
new JSONObject(String): 121 ms (for 100K digits)
new JSONArray(String): 120 ms (for 100K digits)
XML.toJSONObject(String): 123 ms (for 100K digits)
Any endpoint that parses user-controlled JSON or XML through org.json is a potential target, regardless of which specific API method is used.
8. Comparison with Jackson's Treatment of the Same Issue
Jackson 2.15.0 (released April 2023) introduced StreamReadConstraints.withMaxNumberLength() with a default limit of 1,000 digits to address this exact class of vulnerability — and did so without a CVE being filed. This raises a legitimate question: if the Jackson ecosystem handled the same issue without a CVE, why does org.json warrant one?
The two situations are materially different in three ways:
1. Jackson's fix was universal and automatic; org.json's omission is not.
Jackson is the default JSON library in Spring Boot. When Jackson 2.15 shipped with DEFAULT_MAX_NUM_LEN = 1000, the protection was automatically applied to the entire Spring Boot ecosystem without any developer action. Every Spring Boot application upgrading to Spring Boot 3.1+ received the protection transparently.
org.json has no equivalent protection in any version through 20260522. Applications using org.json receive no protection from any upstream fix and have no configurable option to enable it. There is no automatic upgrade path.
2. org.json has an established CVE precedent for the same DoS class.
This project has already been assigned CVEs for resource-exhaustion vulnerabilities triggered by crafted parsing input:
- CVE-2022-45688: XML recursive nesting DoS
- CVE-2023-5072: JSON nested
{[structure DoS
Both were assigned under the same root pattern: parser accepts unbounded user-controlled input, resulting in resource exhaustion. The numeric string length issue is the same pattern in the same library, in a code path that the two prior fixes explicitly left unaddressed. Treating this as a non-CVE while the two structurally identical prior issues were assigned CVEs would be inconsistent.
3. RFC 8259 explicitly permits — and implicitly recommends — setting limits on number length.
RFC 8259 §6 states: "This specification allows implementations to set limits on the range and precision of numbers accepted." The RFC anticipates that secure implementations will impose such limits. org.json's NOTES.md acknowledges only "partial support" for BigInteger/BigDecimal, but provides no size guard and no documentation warning users of the DoS risk. The absence of a documented security boundary in the presence of an RFC-permitted and industry-standard protection mechanism constitutes an exploitable implementation deficiency.
Related Vulnerabilities
Other vulnerabilities affecting the same packages