Skip to content

Security Report: net.minidev:json-smart parser depth-counter leak (CWE-459/CWE-672) #299

Open

Description

@ByteHackr

Date: 27 August 2026
Reporter: Sandipan Roy (saroy@redhat.com)

Summary

Attribute Value
Package net.minidev:json-smart
Class CWE-459 Incomplete Cleanup + CWE-672: instance depth counter leaks across parse() calls on a reused JSONParser
Impact Remote DoS: attacker permanently bricks a shared parser (all later parses fail) or steals depth budget to reject valid payloads
Root cause parse() resets pos but never depth (JSONParserBase.java:210); ~10 mid-parse error paths skip the non-finally clean-close decrements
Distinct from prior CVEs CVE-2023-1370 / CVE-2024-57699 fixed the recursion depth; this is incomplete cleanup + designed instance reuse — separate behavior
Affected range 2.5.0 -> 2.6.0-SNAPSHOT

Vulnerable code

json-smart/src/main/java/net/minidev/json/parser/JSONParserBase.java

Entry (no depth reset):

protected <T> T parse(JsonReaderI<T> mapper) throws ParseException {
    this.pos = -1;                   // depth NOT reset
    return parseInner(mapper);
}
protected <T> T parseNext(JsonReaderI<T> mapper) throws ParseException {
    this.pos = this.pos > 0 ? this.pos : -1;   // depth not reset either
    return parseInner(mapper);
}

Increment / decrement sites (decrements NOT in finally):

case start: if (limitJsonDepth && ++this.depth > MAX_DEPTH) throw new ParseException(...JSON_DEPTH...);
...
case ']':   this.depth--; ...     // only reached on clean close
case '}':   this.depth--; ...

Any throw between an increment and its decrement leaves depth elevated.
JSONParser caches one inner parser per wrapper (getPString() reuse) and
MultipleJsonParser.parseNext() streams many docs through one instance by
design -> leaked counter persists across calls.

Proof of Concept

Poc.java + run-poc.sh (builds both modules from source, compiles, runs).

Poc.java
run-poc.sh

== [3/4] running PoC ==
R1 fresh+{}        -> OK
R2 poison(400)     -> ParseException(Unexpected End Of File position 2000: null)
R3 same,{}         -> ParseException(Malicious payload, having non natural depths, parsing stoped on { at position 0.)   <<< {} on SAME instance REJECTED
R4 fresh-again,{}  -> OK
R5 leak397+depth5  -> ParseException(... non natural depths ... position 15.) <<< valid doc rejected (stolen budget)
VERDICT: depth-counter leak CONFIRMED on reused parser instances
EXIT 3

Controls: fresh guarded instances parse fine (R1, R4); only the reused instance
after an attacker-fed malformed doc fails (R3, R5 demonstrate the defect).

Impact

  • A 400-deep malformed payload permanently poisons the guarded parser: every
    subsequent parse() on the same instance (even {}) throws
    Malicious payload, having non natural depths.
  • Partial budget theft: leak ~397 then a valid 5-deep doc is rejected.
  • Reproduces identically on v2.5.2 and HEAD v2.6.0.

CVSS v3.1

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H = 7.5 HIGH

Suggested fix

protected <T> T parse(JsonReaderI<T> mapper) throws ParseException {
    this.pos  = -1;
    this.depth = 0;                  // ADD
    return parseInner(mapper);
}

and move the clean-close decrements into a try/finally so any error restores
depth irrespective of where it strikes.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions