If you use Jackson to deserialize JSON and have `UNWRAP_SINGLE_VALUE_ARRAYS` enabled, a payload like `[[[[...]]]]` will crash your application via stack exhaustion. Update to 2.13.4 or disable the feature.
A high-severity Denial of Service vulnerability in the ubiquitous FasterXML jackson-databind library. By exploiting the UNWRAP_SINGLE_VALUE_ARRAYS feature with deeply nested JSON arrays, attackers can trigger a StackOverflowError, crashing JVMs with trivial payloads.
If you write Java, you use Jackson. It is the dark matter of the Java ecosystem—invisible, pervasive, and holding everything together. Whether you are building a Spring Boot microservice or a legacy enterprise monolith, jackson-databind is almost certainly the engine parsing your JSON.
But great power comes with great configuration complexity. Jackson has a feature called DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS. It is a quality-of-life setting designed for dealing with quirky APIs that wrap objects in arrays unnecessarily. If enabled, it tells Jackson: "Hey, if you see [{"id":1}], just treat it as {"id":1}."
It sounds helpful, right? A lenient parser is a developer's best friend. But in the security world, leniency is just another word for "attack surface." It turns out that this helpful unwrapping logic was a bit too willing to dive down the rabbit hole, and it forgot to check how deep the hole went.
The vulnerability is a classic case of CWE-674: Uncontrolled Recursion. When UNWRAP_SINGLE_VALUE_ARRAYS is enabled, the BeanDeserializer attempts to handle an incoming array by peeking inside to find the actual object.
Here is the logic flaw: When the deserializer encounters a START_ARRAY token ([), it calls a function to handle the unwrapping. If the very next token is another START_ARRAY, the code recursively calls the deserialization method again. It assumes that eventually, it will hit a JSON object (the "meat" of the data).
The problem is that the computer's stack memory is finite. Each recursive call pushes a new frame onto the stack. If an attacker sends a JSON payload consisting of thousands of opening brackets—[[[[[[...—the parser dutifully creates thousands of stack frames. Eventually, the JVM runs out of stack space and throws a StackOverflowError.
Unlike a regular Exception, a StackOverflowError is an Error type in Java. It is often fatal to the thread processing the request, and in many web server configurations, valid requests can get caught in the crossfire or the entire application container can become unstable.
Let's look at the fix in BeanDeserializer.java. The patch is deceptively simple, proving that the most dangerous bugs often hide in plain sight.
The Vulnerable Logic (Conceptual):
Before the patch, the code essentially said: "If unwrapping is enabled and I see an array, call deserialize again to unpack it."
The Fix (Commit 063183589218fec19a9293ed2f17ec53ea80ba88): The maintainers added a check to ensure that we only unwrap once. If the parser peels back the first layer of the array and immediately sees another array, it realizes it's being tricked and bails out.
// BeanDeserializer.java patch logic
if (_unwrapSingle == Boolean.TRUE ||
((_unwrapSingle == null) && _beanProperties.findFormatFeature(ctxt, _beanType, JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY).asBoolean())) {
JsonToken t = p.nextToken();
// THE FIX: Check if the next token is ALSO an array start
if (t == JsonToken.START_ARRAY) {
return (Object) ctxt.handleUnexpectedToken(_beanType, p);
}
// Proceed with deserialization...
final Object value = deserialize(p, ctxt);
}The key change is the check if (t == JsonToken.START_ARRAY). It explicitly forbids nested arrays when unwrapping is active. It says, "I will unwrap one layer for you, but I am not unwrapping a Russian nesting doll of arrays."
Exploiting this is embarrassingly easy. You don't need shellcode, you don't need memory addresses, and you don't need to bypass ASLR. You just need a text editor and the [ key.
If you identify an endpoint that accepts JSON and has this feature enabled (often done globally in ObjectMapper configuration), you just send this:
[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[...]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]The Attack Chain:
X-Powered-By: Spring or typical stack traces are a giveaway).[{"test":1}]. If the server accepts it and treats it as {"test":1}, the vulnerable feature is likely enabled.deserialize().java.lang.StackOverflowError. If you send enough of these concurrently, you can exhaust the thread pool or crash the JVM entirely.This isn't a complex architectural change. It's a patch update.
Option 1: Update (Preferred)
Upgrade jackson-databind to version 2.13.4, 2.14.0, or later. If you are stuck on the 2.12.x branch, a patch was backported to 2.12.7.1.
Option 2: Configuration Change
If you can't update, verify if you actually need DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS. It is disabled by default. If you turned it on, try turning it off:
objectMapper.disable(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS);Option 3: Stream Constraints (Modern)
In versions 2.15+, Jackson introduced StreamReadConstraints. This allows you to set a hard limit on nesting depth globally, which kills this entire class of vulnerability regardless of specific feature flags:
factory.setStreamReadConstraints(StreamReadConstraints.builder().maxNestingDepth(1000).build());CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
jackson-databind FasterXML | < 2.13.4 | 2.13.4 |
jackson-databind FasterXML | 2.12.0 - 2.12.7 | 2.12.7.1 |
| Attribute | Detail |
|---|---|
| CWE | CWE-674 (Uncontrolled Recursion) |
| CVSS | 7.5 (High) |
| Attack Vector | Network |
| Availability Impact | High (Stack Exhaustion) |
| Exploit Complexity | Low |
| Authentication | None |
Uncontrolled Recursion
Get the latest CVE analysis reports delivered to your inbox.