Skibidi Java - The Infinite Loop in Java Collections; Edge Case to Java Universal DoS    - Checkmarx

Skibidi Java – The Infinite Loop in Java Collections; Edge Case to Java Universal DoS   

7 min.

January 23, 2025

Introduction

Sometimes, not always, but sometimes, the most interesting security discoveries start with someone trying to prove you wrong on social media.  

This is what happened when a user questioned one of our vulnerability reports about a collection library’s implementation of unique lists. What started as a simple Stack Overflow error led us down the rabbit hole and revealed a security flaw affecting Java’s core collection objects, like java.util

We named this SkibidiJava as a nod to how Java’s automatic method calls can create bizarrely dangerous behaviors in complex environments, turning normal and naive operations into, well, unexpected skibidi chaos. 

While this investigation revealed fascinating insights into Java’s core behaviors, it remains in the realm of theoretical research–an intriguing demonstration of how language features can interact in unexpected ways–rather than an observed security vulnerability. 

The Initial Discovery 

It began with a reported StackOverflowError error in Apache Common Collections’ unique list implementation when the list receives itself as an argument, creating a circular reference. While this might sound like a theoretical edge case requiring deep access to code execution, our investigation revealed it could be weaponized through a much more common attack vector: Java serialization.  

The Basic Flaw 

The original flaw manifests when a unique list implementation contains a reference to itself, causing infinite recursion during operations like hashCode(). When collections are used as keys in a HashMap, Java must call hashCode() to determine their bucket location – this is a fundamental part of how HashMaps works. 

 At a glance, it might seem like an unlikely scenario unless we let somebody who wanted to crash our application write it for us. However, attempting to deserialize a HashMap containing our circular-referenced collection as a key will automatically trigger the hashCode() calculation, leading to the StackOverflowError

Here’s a simple demonstration: 

List<Object> list = UniqueList.create(new ArrayList<>()); 
list.add(list); // This creates a circular reference 
list.hashCode(); // This triggers a StackOverflowError 

And now, a slightly “sophisticated” exploit: 

public static void createExploit() throws Exception { 
    ArrayList<Object> al = new ArrayList<Object>(); 
    HashMap<Object, String> map = new HashMap<Object, String>(); 
    map.put(al, "oops"); 
    al.add(al); 
     
    // Serialize 
    ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
    ObjectOutputStream oos = new ObjectOutputStream(baos); 
    oos.writeObject(map); 
    oos.close(); 
     
    // Deserialize (this will crash) 
    ByteArrayInputStream bais = new                                       
    ByteArrayInputStream(baos.toByteArray()); 
    ObjectInputStream ois = new ObjectInputStream(bais); 
    ois.readObject(); // StackOverflowError 
} 

Going Deeper: A Universal Java DoS 

What started as an investigation into a specific collection library led us to believe this wasn’t just about unique lists. The root cause lies in how Java’s core collection classes, java.util, handle recursive operations. Due to the inheritance patterns between Collection and ArrayList, operations that traverse collection elements can enter an infinite recursion loop when circular references are present. 

While many secure applications and frameworks typically restrict deserialization of java.util classes, the fundamental nature of these core Java collections means that homegrown or customized deserializers might allow them, making this flaw particularly concerning. 

Most importantly, this manifests as a StackOverflowError rather than an Exception – a critical distinction in Java’s error hierarchy. While exceptions can be caught with `catch Exception` blocks, Error objects bypass these handlers. Although technically catchable with a catch Error, this is rarely implemented, as Errors represent unrecoverable JVM conditions that shouldn’t be caught, making this an effective vector for DoS attacks. 

The Universal Gadget 

We discovered that this simple pattern could crash virtually any Java deserializer that handles standard collection types: 

ArrayList<Object> al = new ArrayList<Object>(); 
HashMap<Object, String> map = new HashMap<Object, String>(); 
map.put(al, "oops"); 
al.add(al); 

When this structure is serialized and then deserialized, it triggers an infinite recursion between ArrayList and Collection, causing a Denial of Service through stack overflow error. 

Weaponizing the Weakness 

Real-World Impact 

This weakness, manifesting as a vulnerability, becomes particularly dangerous in systems that deserialize untrusted user-provided data into objects without proper validation. While Java often chooses to face deserialization vulnerabilities by allowing certain namespaces, since this particular issue is baked into java.util classes, it could be accidentally allowed.  

Common vulnerable patterns include: 

  1. Message queues that deserialize Java objects 
  1. Remote method invocation services 
  1. APIs that deserialize JSON to Java objects 
  1. Distributed cache systems 
  1. Any framework that deserializes untrusted data 

Mitigation Strategies 

The best mitigation approach will depend on the specific use case, but here are some general strategies: 

  1. Input Validation: Implement strict validation of serialized data before deserialization 
  1. Custom Deserializers: Use custom deserializers that can detect and prevent circular references by limiting object depth and validating; this depth should be tailored for need. 
  1. Allowed Objects: Maintain a strict allowlist of permitted classes for deserialization. 

Sample Mitigation Code 

While the following code demonstrates circular reference detection, note that a complete secure deserialization implementation would also require a secure resolveClass method with proper class allowlisting to prevent standard Java deserialization attacks. 

public class SafeObjectInputStream extends ObjectInputStream { 
    private int maxDepth = 100; // Configurable max depth 
    private Set<Object> seen = new HashSet<>(); 
     
    protected Object resolveObject(Object obj) throws IOException { 
        if (seen.size() > maxDepth) { 
            throw new SecurityException("Potential circular reference detected"); 
        } 
        seen.add(obj); 
        return super.resolveObject(obj); 
    } 
} 

Lessons Learned 

This investigation into Java’s collection classes highlights several key principles for security researchers: 

  1. Even well-established libraries can contain simple but significant security flaws 
  1. “Theoretical” edge-case vulnerabilities can have practical exploit paths 
  1. Core language features need the same security scrutiny as application code 
  1. Don’t dismiss edge cases – they might reveal deeper systemic issues 

Conclusion 

This journey from a “minor” edge case to discovering a universal Java DoS-prone behavior highlights the importance of thorough security research and understanding inheritance patterns in core language features. While the original collection library issue seemed limited in scope, it led us to uncover a more fundamental problem in Java’s collection hierarchy

Read More

Want to learn more? Here are some additional pieces for you to read.