[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[FD] Apache Struts2 JSON Plugin Insecure Deserialisation -- Object Deserialisation Element-Count Limit Bypass



As of the current versions of the Apache Struts2 JSON Plugin (e.g., version 
7.4.0 and the 7.5.0 snapshot dated 28 October 2026), attackers can bypass the 
element-count limit purported to protect Object deserialisation added to said 
code as part of the WW-5618 and S2-072 "hardening controls".  Please note that 
this vulnerability and the attack string is different than the previously 
reported bypass against the JSON array deserialisation, which was discussed in 
detail via "Continued Apache Struts2 JSON Plugin Insecure Deserialisation" (28 
October 2026) and "Maximised JSON Array Attacks -- "Let the Commas Hit the 
Floor" Proof of Concept" (28 October 2026).  Said previous discussions 
pertained strictly to bypassing protections on array deserialisation, with 
distinct code paths, mistakes, and attack strings.

Description:
The information system's JavaScript Object Notation (JSON) object parser 
enforces the `maxElements` resource limit by checking the size of the `HashMap` 
being populated rather than by counting the number of key-value pairs actually 
being parsed, so a payload consisting of entirely repeated keys never grows the 
map past a single entry while incurring both memory and compute costs.

Impact:
An attacker who can send a request body to any endpoint protected by a 
`JSONInterceptor` configured with a `maxElements` ceiling (including the 
default value) can construct a JSON object consisting of an arbitrarily large 
number of duplicate-key pairs -- for example, the same key repeated hundreds of 
thousands of times -- bounded only by the value of `struts.json.maxLength`, and 
have every pair be fully parsed, hashed, and boxed before the `maxElements` 
check ever has a chance to trip since `Map.put(Object, Object)` with an 
already-present key never increases the value `ret.size()`.


Attackers can force disproportionate CPU and allocation work, memory usage 
(similar to that discussed in "Continued Apache Struts2 JSON Plugin Insecure 
Deserialisation" (28 October 2026) and "Maximised JSON Array Attacks -- "Let 
the Commas Hit the Floor" Proof of Concept" (28 October 2026)) with a single 
request and exhaust server resources across concurrent requests.  For example, 
attackers can build a single object containing 262,143 String-Double key-value 
pairs (only limited by `struts.json.maxLength`, to a 2MB string), with a single 
request consuming over 46MB and taking ~37ms to parse and build on a pre-warmed 
system (assume 64-bit HotSpot JVM running with compressed oops and 8-byte 
alignment, which is a standard configuration).  Making several concurrent 
requests of such a nature can quickly create denial of service conditions.



Examples:

Sample Vulnerable Code

`StrutsJSONReader.object()` in the `struts2-json-plugin` module, enforces the 
`maxElements` ceiling by comparing the size of the `HashMap` being populated 
against the limit immediately before every `put(Object, Object)` call, as shown 
below.



```java

    protected Map<String, Object> object() throws JSONException {

        if (this.depth >= this.maxDepth) {

            throw new JSONException("JSON object nesting exceeds maximum 
allowed depth ("

                    + this.maxDepth + "). Use " + JSONConstants.JSON_MAX_DEPTH 
+ " to increase the limit.");

        }

        this.depth++;

        try {

            Map<String, Object> ret = new HashMap<>();

            Object next = this.read();

            if (next != OBJECT_END) {

                String key = (String) next;

                validateKeyLength(key);

                while (this.token != OBJECT_END) {

                    this.read(); // should be a colon



                    if (this.token != OBJECT_END) {

                        if (ret.size() >= this.maxElements) {

                            throw new JSONException("JSON object exceeds 
maximum allowed elements ("

                                    + this.maxElements + "). Use " + 
JSONConstants.JSON_MAX_ELEMENTS + " to increase the limit.");

                        }

                        ret.put(key, this.read());



                        if (this.read() == COMMA) {

                            Object name = this.read();



                            if (name instanceof String nextKey) {

                                key = nextKey;

                                validateKeyLength(key);

                            } else {

                                throw buildInvalidInputException();

                            }

                        }

                    }

                }

            }



            return ret;

        } finally {

            this.depth--;

        }

    }

```

Code snippet from `struts-2-json-plugin`'s 
plugins/json/src/main/java/org/apache/struts2/json/StrutsJSONReader.java, lines 
156-196



Importantly, attackers can further couple this with the previously disclosed 
Apache Struts2 JSON Plugin `maxElements` bypass in the `array()` method in the 
same class (please see the aforementioned Full Disclosure postings for 
details).  While attackers can choose from a variety of Objects, `Double` is 
the most damaging for this particular attack since they incur the cost of 
building and boxing/unboxing and are never cached (unlike `Long` and `String` 
objects).
_______________________________________________
Sent through the Full Disclosure mailing list
https://nmap.org/mailman/listinfo/fulldisclosure
Web Archives & RSS: https://seclists.org/fulldisclosure/