Parsing must reject deeply nested JSON objects and arrays

Issue #583 resolved
created an issue

Attempting to serialize a deeply nested map structure in a JWTClaimsSet causes a StackOverflowError due to excessive recursion in Gson’s serialization process. A claim set like this could be supplied by a client, parsed and logged, causing SOE.

Reproduction

import com.nimbusds.jwt.JWTClaimsSet;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;

public class Test {
    // This builds a claimset with a deeply nested map, which could theoretically be supplied by a client.
    // If the JWT is serialized into JSON (for example, in logging or debugging), it can cause a StackOverflowError.
    public static void main(String[] args) throws ParseException {        
        Map<String, Object> nestedMap = new HashMap<>();
        Map<String, Object> currentLevel = nestedMap;

        for (int i = 0; i < 5000; i++) {
            Map<String, Object> nextLevel = new HashMap<>();
            currentLevel.put("", nextLevel);
            currentLevel = nextLevel;
        }

        JWTClaimsSet claimSet = JWTClaimsSet.parse(nestedMap);

        // This will cause a StackOverflowError due to excessive recursion in GSON's serialization
        claimSet.toString();
    }
}

Comments (9)

  1. Yavor Vasilev

    The current plan is to create a custom com.google.gson.JsonDeserializer and let it check the JSON object nesting depth, before calling the actual Gson deserialize.

    The JWTClaimsSet parse and toJSONobject methods don't throw a StackOverflowError under the same condition. Only the toString which calls the Gson deserializeappears affected.

  2. Daniel Waller

    Hey all,

    I just came across this issue because our dependency management alerted me to a potential DoS vulnerability in Google gson.

    I’ve dug into the issue a bit, and it seems all you’d need to do is update gson to >= 2.12.0.

    They’ve found the problem via fuzzing and implemented a default nesting limit of 255.

    Add nesting limit for `JsonReader` by Marcono1234 · Pull Request #2588 · google/gson · GitHub

    Blaming gson/gson/src/main/java/com/google/gson/stream/JsonReader.java at main · google/gson · GitHub

    Cheers,

    Daniel

  3. Yavor Vasilev

    Our testing with Gson 2.12.1 shows that the toString() continues to throw a StackOverflowError.

    Gson 2.12 changes the parsing to reject nesting deeper than 255, not the serialization. The upgrade to Gson 2.12.1 should still address the original concern though, to prevent parsing of such JSON in the first place.

        public void testSerializationWithDeepJSONObjectNestingCausesStackOverflowError() {
    
            Map<String, Object> jsonObject = JSONObjectUtils.newJSONObject();
    
            Map<String, Object> ref = jsonObject;
    
            for (int i=0; i < 20_000; i++) {
                Map<String, Object> nested = JSONObjectUtils.newJSONObject();
                ref.put("a", nested);
                ref = nested;
            }
    
            JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
                .claim("o", jsonObject)
                .build();
    
            try {
                claimsSet.toString();
                fail();
            } catch (StackOverflowError e) {
                assertNull(e.getMessage());
            }
        }
    

  4. Yavor Vasilev

    Marking as resolved with:

    10.0.2 (2025-02-25)
        * Updates JSONObjectUtils.parse and JSONArrayUtils.parse to reject JSON
          strings with object and array nesting deeper than 255. This change is the
          default parse behaviour of GSon 2.12.0+ (iss #583)
        * Updates GSon to 2.12.1.
    

    Updated tests and Gson bump to 2.12.2: 393a96f

  5. Daniel Waller

    Oh your right, I missed the serialization part

    Awesome! Thanks for the quick fix :)

  6. Log in to comment