Denial of Service by injecting highly recursive collections or maps in XStream
Description
XStream is an open source java library to serialize objects to XML and back again. Versions prior to 1.4.19 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. XStream 1.4.19 monitors and accumulates the time it takes to add elements to collections and throws an exception if a set threshold is exceeded. Users are advised to upgrade as soon as possible. Users unable to upgrade may set the NO_REFERENCE mode to prevent recursion. See GHSA-rmr5-cpv2-vgjf for further details on a workaround if an upgrade is not possible.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
XStream versions prior to 1.4.19 are vulnerable to denial of service via recursive collections, fixed in 1.4.19.
Vulnerability
XStream versions prior to 1.4.19 allow an attacker to cause denial of service by injecting highly recursive collections or maps into the XML stream during unmarshalling [1], [2], [3]. The vulnerability affects all versions up to 1.4.18 [2], [3].
Exploitation
An attacker can craft a malicious XML payload containing deeply nested structures using Java collection types such as java.util.HashMap, HashSet, etc., which trigger exponential hashcode recursion [2], [3]. No authentication is required; the attacker only needs to send the crafted input to an XStream unmarshalling operation [1].
Impact
Successful exploitation results in 100% CPU consumption on the target system, leading to denial of service [1], [3]. The impact is limited to CPU exhaustion; no data integrity or confidentiality is compromised.
Mitigation
Upgrade to XStream 1.4.19 or later, which monitors and limits the time spent adding elements to collections [1], [2], [3]. For users unable to upgrade, set the NO_REFERENCE mode to prevent recursion [1]. Jenkins users affected via the bundled XStream library should upgrade Jenkins to version 2.334 or LTS 2.319.3 [4].
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
com.thoughtworks.xstream:xstreamMaven | < 1.4.19 | 1.4.19 |
Affected products
7- osv-coords6 versionspkg:bitnami/jenkinspkg:maven/com.thoughtworks.xstream/xstreampkg:rpm/suse/xstream&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Development%20Tools%2015%20SP3pkg:rpm/suse/xstream&distro=SUSE%20Linux%20Enterprise%20Real%20Time%2015%20SP2pkg:rpm/suse/xstream&distro=SUSE%20Manager%20Server%20Module%204.1pkg:rpm/suse/xstream&distro=SUSE%20Manager%20Server%20Module%204.2
< 2.319.3+ 5 more
- (no CPE)range: < 2.319.3
- (no CPE)range: < 1.4.19
- (no CPE)range: < 1.4.19-3.18.2
- (no CPE)range: < 1.4.19-3.18.2
- (no CPE)range: < 1.4.19-3.18.2
- (no CPE)range: < 1.4.19-3.18.2
- x-stream/xstreamv5Range: < 1.4.19
Patches
1e8e88621ba1cDescribe and fix CVE-2021-43859.
14 files changed · +568 −21
xstream-distribution/src/content/changes.html+14 −0 modified@@ -39,6 +39,20 @@ <h1 id="upcoming-1.4.x">Upcoming 1.4.x maintenance release</h1> <p>Not yet released.</p> + <p class="highlight">This maintenance release addresses the security vulnerability + <a href="CVE-2021-43859.html">CVE-2021-43859</a>, when unmarshalling highly recursive collections or maps causing a + Denial of Service.</p> + + <h2>API changes</h2> + + <ul> + <li>Added c.t.x.XStream.COLLECTION_UPDATE_LIMIT and c.t.x.XStream.COLLECTION_UPDATE_SECONDS.</li> + <li>Added c.t.x.XStream.setCollectionUpdateLimit(int).</li> + <li>Added c.t.x.core.SecurityUtils.</li> + <li>Added c.t.x.security.AbstractSecurityException and c.t.x.security.InputManipulationException.</li> + <li>c.t.x.security.InputManipulationException derives now from c.t.x.security.AbstractSecurityException.</li> + </ul> + <h1 id="1.4.18">1.4.18</h1> <p>Released August 22, 2021.</p>
xstream-distribution/src/content/CVE-2021-43859.html+199 −0 added@@ -0,0 +1,199 @@ +<html> +<!-- + Copyright (C) 2021 XStream committers. + All rights reserved. + + The software in this package is published under the terms of the BSD + style license a copy of which has been included with this distribution in + the LICENSE.txt file. + + Created on 23. December 2021 by Joerg Schaible + --> + <head> + <title>CVE-2021-43859</title> + </head> + <body> + + <h2 id="vulnerability">Vulnerability</h2> + + <p>CVE-2021-43859: XStream can cause a Denial of Service by injecting highly recursive collections or maps.</p> + + <h2 id="affected_versions">Affected Versions</h2> + + <p>All versions until and including version 1.4.18 are affected.</p> + + <h2 id="description">Description</h2> + + <p>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.</p> + + <h2 id="reproduction">Steps to Reproduce</h2> + + <p>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:</p> + <ul> + <li>java.util.HashMap</li> + <li>java.util.HashSet</li> + <li>java.util.Hashtable</li> + <li>java.util.LinkedHashMap</li> + <li>java.util.LinkedHashSet</li> + <li>java.util.Stack (older Java revisions only)</li> + <li>java.util.Vector (older Java revisions only)</li> + <li>Other third party collection implementations that use their element's hash code may also be affected</li> + </ul> + <p>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:</p> +<div class="Source XML"><pre><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> +</pre></div> +<div class="Source Java"><pre>XStream xstream = new XStream(); +xstream.fromXML(xml); +</pre></div> + <p>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:</p> +<div class="Source XML"><pre><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> +</pre></div> +<div class="Source Java"><pre>XStream xstream = new XStream(); +xstream.fromXML(xml); +</pre></div> + + <p>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.</p> + + <p>Note, this example uses XML, but the attack can be performed for any supported format, that supports references, i.e. + JSON is not affected.</p> + + <h2 id="impact">Impact</h2> + + <p>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.</p> + + <h2 id="workarounds">Workarounds</h2> + + <p>If your object graph does not use referenced elements at all, you may simply set the NO_REFERENCE mode:</p> + +<div class="Source Java"><pre>XStream xstream = new XStream(); +xstream.setMode(XStream.NO_REFERENCES); +</pre></div> + + <p>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:</p> + +<div class="Source Java"><pre>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 +}); +</pre></div> + + <p>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:</p> + +<div class="Source Java"><pre>xstream.addDefaultImplementation(java.util.TreeMap.class, java.util.Map.class); +xstream.addDefaultImplementation(java.util.TreeSet.class, java.util.Set.class); +</pre></div> + + <p>However, this implies that your application does not care about the implementation of the map and all elements are comparable.</p> + + <h2 id="credits">Credits</h2> + + <p>r00t4dm at Cloud-Penetrating Arrow Lab found and reported the issue to XStream and provided the required information to + reproduce it.</p> + + </body> + </html>
xstream-distribution/src/content/security.html+34 −6 modified@@ -30,13 +30,13 @@ context of the server running the XStream process or cause a denial of service by crashing the application or manage to enter an endless loop consuming 100% of CPU cycles.</p> - <p class=highlight>Note: XStream supports other data formats than XML, e.g. JSON. Those formats can be used for - the same attacks.</p> + <p class=highlight>Note: XStream supports other data formats than XML, e.g. JSON. Those formats can usually be used + for the same attacks.</p> - <p>Note, that the XML data can be manipulated on different levels. For example, manipulating values on existing - objects (such as a price value), accessing private data, or breaking the format and causing the XML parser to fail. - The latter case will raise an exception, but the former case must be handled by validity checks in any application - which processes user-supplied XML.</p> + <p>The XML data can be manipulated on different levels. For example, manipulating values on existing objects (such + as a price value), accessing private data, or breaking the format and causing the XML parser to fail. The latter + case will raise an exception, but the former case must be handled by validity checks in any application which + processes user-supplied XML.</p> <h2 id="CVEs">Documented Vulnerabilities</h2> @@ -49,6 +49,14 @@ <h2 id="CVEs">Documented Vulnerabilities</h2> <th>CVE</th> <th>Description</th> </tr> + <tr> + <th>Version 1.4.18</th> + <td></td> + </tr> + <tr> + <th><a href="CVE-2021-43859.html">CVE-2021-43859</a></th> + <td>XStream can cause a Denial of Service by injecting highly recursive collections or maps.</td> + </tr> <tr> <th>Version 1.4.17</th> <td></td> @@ -258,6 +266,16 @@ <h2 id="implicit">Implicit Security</h2> because no-one can assure, that no other vulnerability is found. A better approach is the usage of a whitelist i.e. the allowed class types are setup explicitly. This is the default for XStream 1.4.18 (see below).</p> + <p>XStream supports references to objects already occuring on the object graph in an earlier location. This allows + an attacker to create a highly recursive object structure. Some collections or maps calculate the position of a + member based on the data of the member itself. This is true for sorting collections or maps, but also for + collections or maps based on the hash code of the individual members. The calculation time for the member's + position can increase exponentially depending on the recursive depth of the structure and cause therefore a Denial + of Service. Therefore XStream measures the time consumed to add an element to a collection or map since version + 1.4.19. Normally this operation is performed in a view milliseconds, but if adding elements take longer than a + second, then the time is accumulated and an exception is thrown if it exceeds a definable limit (20 seconds by + default).</p> + <h2 id="explicit">Explicit Security</h2> <p>Starting with XStream 1.4.7, it is possible to define <a href="#framework">permissions</a> for types, to check @@ -285,6 +303,16 @@ <h2 id="explicit">Explicit Security</h2> <p class=highlight>Apart from value manipulations, this implementation still allows the injection of allowed objects at wrong locations, e.g. inserting an integer into a list of strings.</p> + <p>To avoid an attack based on the position of an element in a collection or map, you should also use XStream's + default converters for 3rd party or own implementations of collections or maps. Own custom converters of such + types should measure the time to add an element at deserialization time using the following sequence in the + implementation of the unmarshal method:<div class="Source Java"> +<pre>// unmarshal element of collection +long now = System.currentTimeMillis(); +// add element here, e.g. list.add(element); +SecurityUtils.checkForCollectionDoSAttack(context, now); +</pre></div></p> + <h2 id="validation">XML Validation</h2> <p>XML itself supports input validation using a schema and a validating parser. With XStream, you can use e.g. a
xstream-distribution/src/content/website.xml+1 −0 modified@@ -89,6 +89,7 @@ <page>CVE-2021-39152.html</page> <page>CVE-2021-39153.html</page> <page>CVE-2021-39154.html</page> + <page>CVE-2021-43859.html</page> <page>CVE-2020-26217.html</page> <page>CVE-2020-26258.html</page> <page>CVE-2020-26259.html</page>
xstream/src/java/com/thoughtworks/xstream/converters/collections/CollectionConverter.java+5 −1 modified@@ -1,6 +1,6 @@ /* * Copyright (C) 2003, 2004, 2005 Joe Walnes. - * Copyright (C) 2006, 2007, 2010, 2011, 2013, 2018 XStream Committers. + * Copyright (C) 2006, 2007, 2010, 2011, 2013, 2018, 2021 XStream Committers. * All rights reserved. * * The software in this package is published under the terms of the BSD @@ -13,6 +13,7 @@ import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.core.SecurityUtils; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; import com.thoughtworks.xstream.mapper.Mapper; @@ -96,7 +97,10 @@ protected void populateCollection(HierarchicalStreamReader reader, Unmarshalling protected void addCurrentElementToCollection(HierarchicalStreamReader reader, UnmarshallingContext context, Collection collection, Collection target) { final Object item = readItem(reader, context, collection); // call readBareItem when deprecated method is removed + + long now = System.currentTimeMillis(); target.add(item); + SecurityUtils.checkForCollectionDoSAttack(context, now); } protected Object createCollection(Class type) {
xstream/src/java/com/thoughtworks/xstream/converters/collections/MapConverter.java+5 −1 modified@@ -1,6 +1,6 @@ /* * Copyright (C) 2003, 2004, 2005 Joe Walnes. - * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012, 2013, 2018 XStream Committers. + * Copyright (C) 2006, 2007, 2008, 2010, 2011, 2012, 2013, 2018, 2021 XStream Committers. * All rights reserved. * * The software in this package is published under the terms of the BSD @@ -13,6 +13,7 @@ import com.thoughtworks.xstream.converters.MarshallingContext; import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.core.SecurityUtils; import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.io.HierarchicalStreamWriter; @@ -104,7 +105,10 @@ protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader, Unmarshal Map map, Map target) { final Object key = readCompleteItem(reader, context, map); final Object value = readCompleteItem(reader, context, map); + + long now = System.currentTimeMillis(); target.put(key, value); + SecurityUtils.checkForCollectionDoSAttack(context, now); } protected Object createCollection(Class type) {
xstream/src/java/com/thoughtworks/xstream/converters/extended/NamedMapConverter.java+4 −1 modified@@ -1,5 +1,5 @@ /* - * Copyright (C) 2013, 2016, 2018 XStream Committers. + * Copyright (C) 2013, 2016, 2018, 2021 XStream Committers. * All rights reserved. * * The software in this package is published under the terms of the BSD @@ -21,6 +21,7 @@ import com.thoughtworks.xstream.converters.UnmarshallingContext; import com.thoughtworks.xstream.converters.collections.MapConverter; import com.thoughtworks.xstream.core.JVM; +import com.thoughtworks.xstream.core.SecurityUtils; import com.thoughtworks.xstream.core.util.HierarchicalStreams; import com.thoughtworks.xstream.io.ExtendedHierarchicalStreamWriterHelper; import com.thoughtworks.xstream.io.HierarchicalStreamReader; @@ -339,7 +340,9 @@ protected void populateMap(HierarchicalStreamReader reader, UnmarshallingContext value = valueConverter.fromString(reader.getValue()); } + long now = System.currentTimeMillis(); target.put(key, value); + SecurityUtils.checkForCollectionDoSAttack(context, now); if (entryName != null) { reader.moveUp();
xstream/src/java/com/thoughtworks/xstream/core/SecurityUtils.java+56 −0 added@@ -0,0 +1,56 @@ +/* + * Copyright (C) 2021 XStream Committers. + * All rights reserved. + * + * The software in this package is published under the terms of the BSD + * style license a copy of which has been included with this distribution in + * the LICENSE.txt file. + * + * Created on 21. September 2021 by Joerg Schaible + */ +package com.thoughtworks.xstream.core; + +import com.thoughtworks.xstream.XStream; +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.security.InputManipulationException; + + +/** + * Utility functions for security issues. + * + * @author Jörg Schaible + * @since upcoming + */ +public class SecurityUtils { + + /** + * Check the consumed time adding elements to collections or maps. + * + * Every custom converter should call this method after an unmarshalled element has been added to a collection or + * map. In case of an attack the operation will take too long, because the calculation of the hash code or the + * comparison of the elements in the collection operate on recursive structures. + * + * @param context the unmarshalling context + * @param start the timestamp just before the element was added to the collection or map + * @since upcoming + */ + public static void checkForCollectionDoSAttack(final UnmarshallingContext context, final long start) { + final int diff = (int)((System.currentTimeMillis() - start) / 1000); + if (diff > 0) { + final Integer secondsUsed = (Integer)context.get(XStream.COLLECTION_UPDATE_SECONDS); + if (secondsUsed != null) { + final Integer limit = (Integer)context.get(XStream.COLLECTION_UPDATE_LIMIT); + if (limit == null) { + throw new ConversionException("Missing limit for updating collections."); + } + final int seconds = secondsUsed.intValue() + diff; + if (seconds > limit.intValue()) { + throw new InputManipulationException( + "Denial of Service attack assumed. Adding elements to collections or maps exceeds " + limit.intValue() + " seconds."); + } + context.put(XStream.COLLECTION_UPDATE_SECONDS, new Integer(seconds)); + } + } + } +}
xstream/src/java/com/thoughtworks/xstream/core/TreeUnmarshaller.java+3 −0 modified@@ -26,6 +26,7 @@ import com.thoughtworks.xstream.core.util.PrioritizedList; import com.thoughtworks.xstream.io.HierarchicalStreamReader; import com.thoughtworks.xstream.mapper.Mapper; +import com.thoughtworks.xstream.security.AbstractSecurityException; public class TreeUnmarshaller implements UnmarshallingContext { @@ -74,6 +75,8 @@ protected Object convert(Object parent, Class type, Converter converter) { } catch (final ConversionException conversionException) { addInformationTo(conversionException, type, converter, parent); throw conversionException; + } catch (AbstractSecurityException e) { + throw e; } catch (RuntimeException e) { ConversionException conversionException = new ConversionException(e); addInformationTo(conversionException, type, converter, parent);
xstream/src/java/com/thoughtworks/xstream/security/AbstractSecurityException.java+29 −0 added@@ -0,0 +1,29 @@ +/* + * Copyright (C) 2021 XStream Committers. + * All rights reserved. + * + * Created on 21. September 2021 by Joerg Schaible + */ +package com.thoughtworks.xstream.security; + +import com.thoughtworks.xstream.XStreamException; + + +/** + * General base class for a Security Exception in XStream. + * + * @author Jörg Schaible + * @since upcoming + */ +public abstract class AbstractSecurityException extends XStreamException { + private static final long serialVersionUID = 20210921L; + + /** + * Constructs a SecurityException. + * @param message the exception message + * @since upcoming + */ + public AbstractSecurityException(final String message) { + super(message); + } +}
xstream/src/java/com/thoughtworks/xstream/security/ForbiddenClassException.java+2 −4 modified@@ -1,20 +1,18 @@ /* - * Copyright (C) 2014 XStream Committers. + * Copyright (C) 2014, 2021 XStream Committers. * All rights reserved. * * Created on 08. January 2014 by Joerg Schaible */ package com.thoughtworks.xstream.security; -import com.thoughtworks.xstream.XStreamException; - /** * Exception thrown for a forbidden class. * * @author Jörg Schaible * @since 1.4.7 */ -public class ForbiddenClassException extends XStreamException { +public class ForbiddenClassException extends AbstractSecurityException { /** * Construct a ForbiddenClassException.
xstream/src/java/com/thoughtworks/xstream/security/InputManipulationException.java+27 −0 added@@ -0,0 +1,27 @@ +/* + * Copyright (C) 2021 XStream Committers. + * All rights reserved. + * + * Created on 21. September 2021 by Joerg Schaible + */ +package com.thoughtworks.xstream.security; + + +/** + * Class for a Security Exception assuming input manipulation in XStream. + * + * @author Jörg Schaible + * @since upcoming + */ +public class InputManipulationException extends AbstractSecurityException { + private static final long serialVersionUID = 20210921L; + + /** + * Constructs a SecurityException. + * @param message the exception message + * @since upcoming + */ + public InputManipulationException(final String message) { + super(message); + } +}
xstream/src/java/com/thoughtworks/xstream/XStream.java+40 −2 modified@@ -151,6 +151,7 @@ import com.thoughtworks.xstream.mapper.XStream11XmlFriendlyMapper; import com.thoughtworks.xstream.security.AnyTypePermission; import com.thoughtworks.xstream.security.ArrayTypePermission; +import com.thoughtworks.xstream.security.InputManipulationException; import com.thoughtworks.xstream.security.ExplicitTypePermission; import com.thoughtworks.xstream.security.InterfaceTypePermission; import com.thoughtworks.xstream.security.NoPermission; @@ -295,6 +296,8 @@ public class XStream { // CAUTION: The sequence of the fields is intentional for an optimal XML output of a // self-serialization! + private int collectionUpdateLimit = 20; + private ReflectionProvider reflectionProvider; private HierarchicalStreamDriver hierarchicalStreamDriver; private ClassLoaderReference classLoaderReference; @@ -329,6 +332,9 @@ public class XStream { public static final int PRIORITY_LOW = -10; public static final int PRIORITY_VERY_LOW = -20; + public static final String COLLECTION_UPDATE_LIMIT = "XStreamCollectionUpdateLimit"; + public static final String COLLECTION_UPDATE_SECONDS = "XStreamCollectionUpdateSeconds"; + private static final String ANNOTATION_MAPPER_TYPE = "com.thoughtworks.xstream.mapper.AnnotationMapper"; private static final Pattern IGNORE_ALL = Pattern.compile(".*"); @@ -1182,6 +1188,23 @@ public void setMarshallingStrategy(MarshallingStrategy marshallingStrategy) { this.marshallingStrategy = marshallingStrategy; } + /** + * Set time limit for adding elements to collections or maps. + * + * Manipulated content may be used to create recursive hash code calculations or sort operations. An + * {@link InputManipulationException} is thrown, it the summed up time to add elements to collections or maps + * exceeds the provided limit. + * + * Note, that the time to add an individual element is calculated in seconds, not milliseconds. However, attacks + * typically use objects with exponential growing calculation times. + * + * @param maxSeconds limit in seconds or 0 to disable check + * @since upcoming + */ + public void setCollectionUpdateLimit(int maxSeconds) { + collectionUpdateLimit = maxSeconds; + } + /** * Serialize an object to a pretty-printed XML String. * @@ -1388,6 +1411,13 @@ public Object unmarshal(HierarchicalStreamReader reader, Object root) { */ public Object unmarshal(HierarchicalStreamReader reader, Object root, DataHolder dataHolder) { try { + if (collectionUpdateLimit >= 0) { + if (dataHolder == null) { + dataHolder = new MapBackedDataHolder(); + } + dataHolder.put(COLLECTION_UPDATE_LIMIT, new Integer(collectionUpdateLimit)); + dataHolder.put(COLLECTION_UPDATE_SECONDS, new Integer(0)); + } return marshallingStrategy.unmarshal(root, reader, dataHolder, converterLookup, mapper); } catch (ConversionException e) { Package pkg = getClass().getPackage(); @@ -2053,15 +2083,23 @@ public ObjectInputStream createObjectInputStream(final HierarchicalStreamReader * @see #createObjectInputStream(com.thoughtworks.xstream.io.HierarchicalStreamReader) * @since 1.4.10 */ - public ObjectInputStream createObjectInputStream(final HierarchicalStreamReader reader, final DataHolder dataHolder) + public ObjectInputStream createObjectInputStream(final HierarchicalStreamReader reader, DataHolder dataHolder) throws IOException { + if (collectionUpdateLimit >= 0) { + if (dataHolder == null) { + dataHolder = new MapBackedDataHolder(); + } + dataHolder.put(COLLECTION_UPDATE_LIMIT, new Integer(collectionUpdateLimit)); + dataHolder.put(COLLECTION_UPDATE_SECONDS, new Integer(0)); + } + final DataHolder dh = dataHolder; return new CustomObjectInputStream(new CustomObjectInputStream.StreamCallback() { public Object readFromStream() throws EOFException { if (!reader.hasMoreChildren()) { throw new EOFException(); } reader.moveDown(); - final Object result = unmarshal(reader, null, dataHolder); + final Object result = unmarshal(reader, null, dh); reader.moveUp(); return result; }
xstream/src/test/com/thoughtworks/acceptance/SecurityVulnerabilityTest.java+149 −6 modified@@ -17,13 +17,20 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; -import com.thoughtworks.xstream.XStreamException; import com.thoughtworks.xstream.converters.ConversionException; import com.thoughtworks.xstream.core.JVM; import com.thoughtworks.xstream.security.AnyTypePermission; import com.thoughtworks.xstream.security.ForbiddenClassException; +import com.thoughtworks.xstream.security.InputManipulationException; import com.thoughtworks.xstream.security.ProxyTypePermission; @@ -56,9 +63,9 @@ public void testCannotInjectEventHandler() { try { xstream.fromXML(xml); - fail("Thrown " + XStreamException.class.getName() + " expected"); - } catch (final XStreamException e) { - assertTrue(e.getMessage().indexOf(EventHandler.class.getName()) > 0); + fail("Thrown " + ForbiddenClassException.class.getName() + " expected"); + } catch (final ForbiddenClassException e) { + // OK } assertEquals(0, BUFFER.length()); } @@ -126,7 +133,7 @@ public void exec() { public void testInstanceOfVoid() { try { xstream.fromXML("<void/>"); - fail("Thrown " + ConversionException.class.getName() + " expected"); + fail("Thrown " + ForbiddenClassException.class.getName() + " expected"); } catch (final ForbiddenClassException e) { // OK } @@ -163,7 +170,7 @@ public void testCannotUseJaxwsInputStreamToDeleteFile() { xstream.aliasType("is", InputStream.class); try { xstream.fromXML(xml); - fail("Thrown " + ConversionException.class.getName() + " expected"); + fail("Thrown " + ForbiddenClassException.class.getName() + " expected"); } catch (final ForbiddenClassException e) { // OK } @@ -261,4 +268,140 @@ public void testExplicitlyUnmarshalEndlessByteArryInputStream() { assertEquals("ArrayIndexOutOfBoundsException expected reading invalid stream", 5, i); } } + + public void testDoSAttackWithHashSet() { + final Set set = new HashSet(); + Set s1 = set; + Set s2 = new HashSet(); + for (int i = 0; i < 30; i++) { + final Set t1 = new HashSet(); + final Set t2 = new HashSet(); + t1.add("a"); + t2.add("b"); + s1.add(t1); + s1.add(t2); + s2.add(t2); + s2.add(t1); + s1 = t1; + s2 = t2; + } + + xstream.setCollectionUpdateLimit(5); + final String xml = xstream.toXML(set); + try { + + xstream.fromXML(xml); + fail("Thrown " + InputManipulationException.class.getName() + " expected"); + } catch (final InputManipulationException e) { + assertTrue("Limit expected in message", e.getMessage().contains("exceeds 5 seconds")); + } + } + + public void testDoSAttackWithLinkedHashSet() { + final Set set = new LinkedHashSet(); + Set s1 = set; + Set s2 = new LinkedHashSet(); + for (int i = 0; i < 30; i++) { + final Set t1 = new LinkedHashSet(); + final Set t2 = new LinkedHashSet(); + t1.add("a"); + t2.add("b"); + s1.add(t1); + s1.add(t2); + s2.add(t2); + s2.add(t1); + s1 = t1; + s2 = t2; + } + + xstream.setCollectionUpdateLimit(5); + final String xml = xstream.toXML(set); + try { + xstream.fromXML(xml); + fail("Thrown " + InputManipulationException.class.getName() + " expected"); + } catch (final InputManipulationException e) { + assertTrue("Limit expected in message", e.getMessage().contains("exceeds 5 seconds")); + } + } + + public void testDoSAttackWithHashMap() { + final Map map = new HashMap(); + Map m1 = map; + Map m2 = new HashMap(); + for (int i = 0; i < 25; i++) { + final Map t1 = new HashMap(); + final Map t2 = new HashMap(); + t1.put("a", "b"); + t2.put("c", "d"); + m1.put(t1, t2); + m1.put(t2, t1); + m2.put(t2, t1); + m2.put(t1, t2); + m1 = t1; + m2 = t2; + } + xstream.setCollectionUpdateLimit(5); + + final String xml = xstream.toXML(map); + try { + xstream.fromXML(xml); + fail("Thrown " + InputManipulationException.class.getName() + " expected"); + } catch (InputManipulationException e) { + assertTrue("Limit expected in message", e.getMessage().contains("exceeds 5 seconds")); + } + } + + public void testDoSAttackWithLinkedHashMap() { + final Map map = new LinkedHashMap(); + Map m1 = map; + Map m2 = new LinkedHashMap(); + for (int i = 0; i < 25; i++) { + final Map t1 = new LinkedHashMap(); + final Map t2 = new LinkedHashMap(); + t1.put("a", "b"); + t2.put("c", "d"); + m1.put(t1, t2); + m1.put(t2, t1); + m2.put(t2, t1); + m2.put(t1, t2); + m1 = t1; + m2 = t2; + } + + xstream.setCollectionUpdateLimit(5); + final String xml = xstream.toXML(map); + try { + xstream.fromXML(xml); + fail("Thrown " + InputManipulationException.class.getName() + " expected"); + } catch (final InputManipulationException e) { + assertTrue("Limit expected in message", e.getMessage().contains("exceeds 5 seconds")); + } + } + + public void testDoSAttackWithHashtable() { + final Map map = new Hashtable(); + Map m1 = map; + Map m2 = new Hashtable(); + for (int i = 0; i < 100; i++) { + final Map t1 = new Hashtable(); + final Map t2 = new Hashtable(); + t1.put("a", "b"); + t2.put("c", "d"); + m1.put(t1, t2); + m1.put(t2, t1); + m2.put(t2, t1); + m2.put(t1, t2); + m1 = t1; + m2 = t2; + } + + xstream.setCollectionUpdateLimit(5); + final String xml = xstream.toXML(map); + try { + xstream.fromXML(xml); + fail("Thrown " + InputManipulationException.class.getName() + " expected"); + } catch (final InputManipulationException e) { + assertTrue("Limit expected in message", e.getMessage().contains("exceeds 5 seconds")); + } + } }
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
16- github.com/advisories/GHSA-rmr5-cpv2-vgjfghsaADVISORY
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/VACQYG356OHUTD5WQGAQ4L2TTFTAV3SJ/mitrevendor-advisoryx_refsource_FEDORA
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/XODFRE2ZL64FICBJDOPWOLPTSSAI4U7X/mitrevendor-advisoryx_refsource_FEDORA
- nvd.nist.gov/vuln/detail/CVE-2021-43859ghsaADVISORY
- www.openwall.com/lists/oss-security/2022/02/09/1ghsamailing-listx_refsource_MLISTWEB
- github.com/x-stream/xstream/commit/e8e88621ba1c85ac3b8620337dd672e0c0c3a846ghsax_refsource_MISCWEB
- github.com/x-stream/xstream/security/advisories/GHSA-rmr5-cpv2-vgjfghsax_refsource_CONFIRMWEB
- lists.debian.org/debian-lts-announce/2022/02/msg00018.htmlghsamailing-listx_refsource_MLISTWEB
- lists.debian.org/debian-lts-announce/2024/12/msg00023.htmlghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/VACQYG356OHUTD5WQGAQ4L2TTFTAV3SJghsaWEB
- lists.fedoraproject.org/archives/list/package-announce%40lists.fedoraproject.org/message/XODFRE2ZL64FICBJDOPWOLPTSSAI4U7XghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/VACQYG356OHUTD5WQGAQ4L2TTFTAV3SJghsaWEB
- lists.fedoraproject.org/archives/list/package-announce@lists.fedoraproject.org/message/XODFRE2ZL64FICBJDOPWOLPTSSAI4U7XghsaWEB
- www.oracle.com/security-alerts/cpuapr2022.htmlghsax_refsource_MISCWEB
- www.oracle.com/security-alerts/cpujul2022.htmlghsax_refsource_MISCWEB
- x-stream.github.io/CVE-2021-43859.htmlghsax_refsource_MISCWEB
News mentions
1- Jenkins Security Advisory 2022-02-09Jenkins Security Advisories · Feb 9, 2022