GCVE Workshop - 22 September 2026 (14:00-18:00), Luxembourg Before The Vulnopticon Conference - Registration

GCVE-1988-2026-0240

Vulnerability from gna-1988 – Published: 2026-09-08 08:13 – Updated: 2026-09-11 11:13
VLAI
Title
JSON Deserialiser Unconstrained Resource Consumption Quick Overview
Summary
As previously mentioned, via "Struts2 and Related Framework Array/Collection DoS" (26 October 2025), hundreds of JavaScript object notation (JSON) libraries are vulnerable to unconstrained resource consumption through large JSON arrays, which, when deserialised, create arbitrarily large collections/arrays/data structures. This work looks specifically at the Apache Struts2 JSON Plugin, using it as an example for why this vulnerability exists, how to exploit it. Understanding Deserialisation There are, regardless of the library, language, three methods of deserialisating data: 1. Call constructors 2. Call setters 3. Set the variable directly Most systems opt for #2, at least by default, and for a variety of reasons. By leveraging setters (and serialisation then often uses getters), the deserialiser needn't reflect into non-public or static structures - they simply use the default constructor to create the base object, then call to the referenced or mapped public methods. This means that the deserialiser, which has to use reflection as part of the process (even if that reflection is obscured - there are exceptions but they are not relevant to this discussion and, even then, almost always still have reflection, even if outside of the purview of the purported library), doesn't need to allow reflection to override visibility or allow static references, either of which open the system up to a large number of attacks. While option #1 also can allow the same "safer" reflection than option #3, it creates "bloat" with complex constructors, multiple constructors just to rehydrate an object, so is less favoured by both developers picking a deserialiser and individuals writing the deserialisers. Option #3 requires the variables to be either directly exposed as public variables, which makes race conditions and other issues more likely, gives up control over the variable and shaping it (e.g., performing input validation, sanitisation, and escaping as it flows into the object), etc., or requires the deserialiser to allow reflecting into private variables, which makes the deserialiser a massive target. Both Struts2 and the Struts2 JSON Plugin prefer to use setters and getters for the deserialisation/serialisation process (notably, a deserialiser need not include a serialiser and vice versa). The Flow When a user makes a request to Apache Struts2, the data flows through the StrutsPrepareAndExecuteFilter to all applicable ServletFilters, then to the ActionMapper, the ActionProxy, all configured Interceptors, and eventually to the mapped Action. The deserialisers - be they the default Apache Struts2 deserialiser, the Apache Struts2 JSON Plugin, or something else - are interceptors. To help the reader visualise and understand this dataflow, we have created the sequence diagram below. [cid:image005.png@01DCADCB.ACA14A10] The Apache Struts2 JSON Plugin, itself, is composed of multiple classes, but the classes of importance for this discussion are the JSONInterceptor, JSONUtil, JSONReader, and JSONPopulator. The following is a high-level diagram showing the data flow of interest for this discussion - specifically focusing on deserialisation of JSON arrays as the JSON flows through the library. [cid:image006.png@01DCADCB.ACA14A10] Vulnerable Code The vulnerable code, in this example, is contained within JSONReader, which is responsible for rehydration of the JSON string into either a Map or a List, which is then bubbled up to the JSONUtil, returned to the JSONInterceptor (via Object obj = JSONUtil.deserialize(request.getReader())), translated into a Map if it is a list, and then the Map is passed to the JSONPopulator, which is nothing more than a standard reflective layer that builds the objects, sets the variables using the default constructor to instantiate objects and setters (if it can find them) to set the variables. Below is some of the offending code that is vulnerable to trivial resource exhaustion, from JSONReader: protected List array() throws JSONException { List ret = new ArrayList(); Object value = this.read(); while (this.token != ARRAY_END) { ret.add(value); Object read = this.read(); if (read == COMMA) { value = this.read(); } else if (read != ARRAY_END) { throw buildInvalidInputException(); } } return ret; } Notably, this method foolishly will keep reading until it reaches a JSON array terminator -- `]`. Attackers can, as such, simply send large arrays and the reader will continuously create new Java Object instances and add them to the `ret` ArrayList. The protected Map object() method suffers similarly, endlessly adding Object instances to the `ret` HashMap. In fact, this paradigm is peppered throughout this code and that of, again, literally hundreds of JSON deserialisers. There are a few things to understand about why this is dangerous. First, from a language-specific perspective, ArrayList and HashMap experience automatic growth and both default to a rather small capacity (10 and 16, respectively) and grow rather quickly (~50% and ~100% capacity increase, respectively). HashMap growth triggers when the size (number of elements in the instance) exceeds the threshold (capacity * loadfactor, or put another way, capacity * 0.75). ArrayList grows only when one more element is added than it has capacity. The growth operation for both is O(n), where n is the number of elements, but the memory impact is far greater than the compute, which, itself becomes sizable quickly, since the memory must be allocated for the new data structure while the old still exists - for a HashMap, that means that you go from n to 3n, since the size doubles (2n) but the original is still in memory during the copy operation. For an ArrayList, it is closer to 2.5 - the size increases to 1.5n and the original n remain in memory during the copy operation. Of course, on top of this, you have garbage collection, so the old data structures - which are simply arrays - remain until they are cleaned up. Outside of the language-specific perspective, attackers can simply create arbitrarily large JSON arrays and, even if simply null, they will result in stuffing entries into data structures. Attackers can simply exhaust memory, especially if they run just a few concurrent instances of malicious requests. Even if attackers cannot exhaust memory, they can exhaust compute - the information system must parse the entire array, must build out the data structure, must then map the data structure out, and must then attempt to stuff the data into the rehydrated object. In this way, the attack operates to target both processor and memory of the victim system and has been used to successfully bring down hundreds of thousands of information systems within seconds and with just a few requests. The Attack Much like a "ping of death", "zip bomb", or related non-volumetric denial of service attack, the attacker simply makes a request that forces unbounded memory and compute: { "id": "pizza", "parts": [ null, null, null, null, ...<<14,000,000+>>, null ] } To facilitate this, a simple Python script can be made that prebuilds the payload, inserting millions of "null," entries into the JSON array. The attacker then simply sends a few concurrent instances of the packet. Wonderfully, if using "null,", each part is only 5 characters, so these attacks aren't necessarily very many megabytes (70MB) and, realistically, resource constrained environments, heavily used systems, etc., will struggle with smaller payloads - attackers can adjust the levers by decreasing payload size and, if needed, increasing the number of concurrent requests. Mitigating Realistically, if the JSON is in the body, setting body size limits on systems that aren't especially resource constrained can help mitigate this attack. While you could look for large numbers of "null," entries, attackers could simply send garbage objects, strings, instead - the deserialiser doesn't know or care what the actual data structure it is reflecting into at this point, so attackers could give anything, because it's merely building out the mapping, which is where the "evil" is occurring, and the reflection, which would try to map the objects to actual data in the supposedly serialised object, has not happened. _______________________________________________ Sent through the Full Disclosure mailing list https://nmap.org/mailman/listinfo/fulldisclosure Web Archives & RSS: https://seclists.org/fulldisclosure/
Severity
No CVSS data available.
Impacted products
Vendor Product Version CPE status
Json Deserialiser Unconstrained Affected: unknown
guessed Create a notification for this product.

{
  "containers": {
    "cna": {
      "affected": [
        {
          "product": "Deserialiser Unconstrained",
          "vendor": "Json",
          "versions": [
            {
              "status": "affected",
              "version": "unknown"
            }
          ]
        }
      ],
      "credits": [
        {
          "lang": "en",
          "type": "finder",
          "value": "Daniel Owens via Fulldisclosure"
        }
      ],
      "descriptions": [
        {
          "lang": "en",
          "value": "As previously mentioned, via \"Struts2 and Related Framework Array/Collection DoS\" (26 October 2025), hundreds of \nJavaScript object notation (JSON) libraries are vulnerable to unconstrained resource consumption through large JSON \narrays, which, when deserialised, create arbitrarily large collections/arrays/data structures.  This work looks \nspecifically at the Apache Struts2 JSON Plugin, using it as an example for why this vulnerability exists, how to \nexploit it.\n\nUnderstanding Deserialisation\nThere are, regardless of the library, language, three methods of deserialisating data:\n\n\n  1.  Call constructors\n  2.  Call setters\n  3.  Set the variable directly\n\nMost systems opt for #2, at least by default, and for a variety of reasons.  By leveraging setters (and serialisation \nthen often uses getters), the deserialiser needn\u0027t reflect into non-public or static structures - they simply use the \ndefault constructor to create the base object, then call to the referenced or mapped public methods.  This means that \nthe deserialiser, which has to use reflection as part of the process (even if that reflection is obscured - there are \nexceptions but they are not relevant to this discussion and, even then, almost always still have reflection, even if \noutside of the purview of the purported library), doesn\u0027t need to allow reflection to override visibility or allow \nstatic references, either of which open the system up to a large number of attacks.  While option #1 also can allow the \nsame \"safer\" reflection than option #3, it creates \"bloat\" with complex constructors, multiple constructors just to \nrehydrate an object, so is less favoured by both developers picking a deserialiser and individuals writing the \ndeserialisers.  Option #3 requires the variables to be either directly exposed as public variables, which makes race \nconditions and other issues more likely, gives up control over the variable and shaping it (e.g., performing input \nvalidation, sanitisation, and escaping as it flows into the object), etc., or requires the deserialiser to allow \nreflecting into private variables, which makes the deserialiser a massive target.\n\nBoth Struts2 and the Struts2 JSON Plugin prefer to use setters and getters for the deserialisation/serialisation \nprocess (notably, a deserialiser need not include a serialiser and vice versa).\n\nThe Flow\nWhen a user makes a request to Apache Struts2, the data flows through the StrutsPrepareAndExecuteFilter to all \napplicable ServletFilters, then to the ActionMapper, the ActionProxy, all configured Interceptors, and eventually to \nthe mapped Action.  The deserialisers - be they the default Apache Struts2 deserialiser, the Apache Struts2 JSON \nPlugin, or something else - are interceptors.  To help the reader visualise and understand this dataflow, we have \ncreated the sequence diagram below.\n\n[cid:image005.png@01DCADCB.ACA14A10]\n\nThe Apache Struts2 JSON Plugin, itself, is composed of multiple classes, but the classes of importance for this \ndiscussion are the JSONInterceptor, JSONUtil, JSONReader, and JSONPopulator.  The following is a high-level diagram \nshowing the data flow of interest for this discussion - specifically focusing on deserialisation of JSON arrays as the \nJSON flows through the library.\n\n[cid:image006.png@01DCADCB.ACA14A10]\n\nVulnerable Code\nThe vulnerable code, in this example, is contained within JSONReader, which is responsible for rehydration of the JSON \nstring into either a Map or a List, which is then bubbled up to the JSONUtil, returned to the JSONInterceptor (via \nObject obj = JSONUtil.deserialize(request.getReader())), translated into a Map if it is a list, and then the Map is \npassed to the JSONPopulator, which is nothing more than a standard reflective layer that builds the objects, sets the \nvariables using the default constructor to instantiate objects and setters (if it can find them) to set the variables.  \nBelow is some of the offending code that is vulnerable to trivial resource exhaustion, from JSONReader:\n\n\n   protected List array() throws JSONException {\n        List ret = new ArrayList();\n        Object value = this.read();\n        while (this.token != ARRAY_END) {\n            ret.add(value);\n            Object read = this.read();\n            if (read == COMMA) {\n                value = this.read();\n            } else if (read != ARRAY_END) {\n                throw buildInvalidInputException();\n            }\n        }\n        return ret;\n    }\n\n\nNotably, this method foolishly will keep reading until it reaches a JSON array terminator -- `]`.  Attackers can, as \nsuch, simply send large arrays and the reader will continuously create new Java Object instances and add them to the \n`ret` ArrayList.  The protected Map object() method suffers similarly, endlessly adding Object instances to the `ret` \nHashMap.  In fact, this paradigm is peppered throughout this code and that of, again, literally hundreds of JSON \ndeserialisers.\n\nThere are a few things to understand about why this is dangerous.\n\nFirst, from a language-specific perspective, ArrayList and HashMap experience automatic growth and both default to a \nrather small capacity (10 and 16, respectively) and grow rather quickly (~50% and ~100% capacity increase, \nrespectively).  HashMap growth triggers when the size (number of elements in the instance) exceeds the threshold \n(capacity * loadfactor, or put another way, capacity * 0.75).  ArrayList grows only when one more element is added than \nit has capacity.  The growth operation for both is O(n), where n is the number of elements, but the memory impact is \nfar greater than the compute, which, itself becomes sizable quickly, since the memory must be allocated for the new \ndata structure while the old still exists - for a HashMap, that means that you go from n to 3n, since the size doubles \n(2n) but the original is still in memory during the copy operation.  For an ArrayList, it is closer to 2.5 - the size \nincreases to 1.5n and the original n remain in memory during the copy operation.  Of course, on top of this, you have \ngarbage collection, so the old data structures - which are simply arrays - remain until they are cleaned up.\n\nOutside of the language-specific perspective, attackers can simply create arbitrarily large JSON arrays and, even if \nsimply null, they will result in stuffing entries into data structures.  Attackers can simply exhaust memory, \nespecially if they run just a few concurrent instances of malicious requests.  Even if attackers cannot exhaust memory, \nthey can exhaust compute - the information system must parse the entire array, must build out the data structure, must \nthen map the data structure out, and must then attempt to stuff the data into the rehydrated object.\n\nIn this way, the attack operates to target both processor and memory of the victim system and has been used to \nsuccessfully bring down hundreds of thousands of information systems within seconds and with just a few requests.\n\nThe Attack\nMuch like a \"ping of death\", \"zip bomb\", or related non-volumetric denial of service attack, the attacker simply makes \na request that forces unbounded memory and compute:\n\n{\n  \"id\": \"pizza\",\n  \"parts\": [\n    null,\n    null,\n    null,\n    null,\n    ...\u003c\u003c14,000,000+\u003e\u003e,\n    null\n  ]\n}\n\nTo facilitate this, a simple Python script can be made that prebuilds the payload, inserting millions of \"null,\" \nentries into the JSON array.  The attacker then simply sends a few concurrent instances of the packet.  Wonderfully, if \nusing \"null,\", each part is only 5 characters, so these attacks aren\u0027t necessarily very many megabytes (70MB) and, \nrealistically, resource constrained environments, heavily used systems, etc., will struggle with smaller payloads - \nattackers can adjust the levers by decreasing payload size and, if needed, increasing the number of concurrent requests.\n\nMitigating\nRealistically, if the JSON is in the body, setting body size limits on systems that aren\u0027t especially resource \nconstrained can help mitigate this attack.  While you could look for large numbers of \"null,\" entries, attackers could \nsimply send garbage objects, strings, instead - the deserialiser doesn\u0027t know or care what the actual data structure it \nis reflecting into at this point, so attackers could give anything, because it\u0027s merely building out the mapping, which \nis where the \"evil\" is occurring, and the reflection, which would try to map the objects to actual data in the \nsupposedly serialised object, has not happened.\n_______________________________________________\nSent through the Full Disclosure mailing list\nhttps://nmap.org/mailman/listinfo/fulldisclosure\nWeb Archives \u0026 RSS: https://seclists.org/fulldisclosure/"
        }
      ],
      "providerMetadata": {
        "dateUpdated": "2026-09-11T11:13:19Z",
        "orgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
        "shortName": "VULNARCHIVE"
      },
      "references": [
        {
          "tags": [
            "technical-description"
          ],
          "url": "https://vuln.freearchive.org/archive/full-disclosure/2026/Mar/6"
        },
        {
          "tags": [
            "technical-description"
          ],
          "url": "https://seclists.org/fulldisclosure/2026/Mar/6"
        },
        {
          "url": "https://nmap.org/mailman/listinfo/fulldisclosure"
        },
        {
          "url": "https://seclists.org/fulldisclosure/"
        }
      ],
      "source": {
        "defect": [
          "https://seclists.org/fulldisclosure/2026/Mar/6"
        ],
        "discovery": "EXTERNAL"
      },
      "title": "JSON Deserialiser Unconstrained Resource Consumption Quick Overview",
      "x_gcve": [
        {
          "recordType": "advisory",
          "relationships": [],
          "vulnId": "GCVE-1988-2026-0240",
          "x_vulnarchive": {
            "archiveUrl": "https://vuln.freearchive.org/archive/full-disclosure/2026/Mar/6",
            "automated": true,
            "contentSha256": "072c67f0dd0001a757cd820e988e829a90e89d1051c1eeb103a88ef7533ba365",
            "evidenceScore": 7,
            "messageId": "",
            "originalUrl": "https://seclists.org/fulldisclosure/2026/Mar/6",
            "policy": "vulnarchive-1",
            "sourceFormat": "text/html",
            "sourcePublishedAt": "2026-03-07T05:45:20Z"
          }
        }
      ]
    }
  },
  "cveMetadata": {
    "assignerOrgId": "4e2abfbf-4a2a-4b76-a4e0-d77c18ba156c",
    "assignerShortName": "VULNARCHIVE",
    "datePublished": "2026-09-08T08:13:41Z",
    "dateUpdated": "2026-09-11T11:13:19Z",
    "state": "PUBLISHED",
    "vulnId": "GCVE-1988-2026-0240"
  },
  "dataType": "CVE_RECORD",
  "dataVersion": "5.2"
}



Log in or create an account to share your comment.




Tags
Taxonomy of the tags.


Loading…

Loading…

Loading…

Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.

Sightings

Author Source Type Date Other

Nomenclature

  • Seen: The vulnerability was mentioned, discussed, or observed by the user.
  • Confirmed: The vulnerability has been validated from an analyst's perspective.
  • Published Proof of Concept: A public proof of concept is available for this vulnerability.
  • Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
  • Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
  • Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
  • Not confirmed: The user expressed doubt about the validity of the vulnerability.
  • Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.

Loading…

Detection rules are retrieved from Rulezet.

Loading…

Loading…

Loading…