Security Aspects

XStream is designed to be an easy to use library. It takes its main task seriously: converting Java objects to XML, and XML to Java objects. As a result, it is possible to create an instance of XStream with the default constructor, call a method to convert an object into XML, then call another method to turn the XML back into an equivalent Java object. By design, there are few limits to the type of objects XStream can handle.

This flexibility comes at a price. XStream applies various techniques under the hood to ensure it is able to handle all types of objects. This includes using undocumented Java features and reflection. The XML generated by XStream includes all information required to build objects of almost any type. This introduces a potential security problem.

The provided XML data is used by XStream to unmarshal Java objects. This data can be manipulated by injecting the XML representation of other objects, that were not present at marshalling time. An attacker could take advantage of this to access private data, delete local files, execute arbitrary code or shell commands in the 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.

Note: XStream supports other data formats than XML, e.g. JSON. Those formats can usually be used for the same attacks.

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.

Documented Vulnerabilities

Over the years, several of these attacks have been reported and documented in the Common Vulnerability and Exposure (CVE) system managed by the Mitre Corporation. Following a list of the reported vulnerabilities for the different versions:

CVE Description
Version 1.4.19
CVE-2022-41966 XStream can cause a Denial of Service by injecting recursive collections or maps based on element's hash values raising a stack overflow.
CVE-2022-40151 XStream can cause a Denial of Service by injecting deeply nested objects raising a stack overflow.
CVE-2022-40152 to CVE-2022-40156 Although unconfirmed these issues had been assigned to XStream nonetheless. However, all these issues are caused by the same problem in Woodstox. Therefore CVE-2022-40152 has been officially reassigned and the other CVEs have been revoked.
Version 1.4.18
CVE-2021-43859 XStream can cause a Denial of Service by injecting highly recursive collections or maps.
Version 1.4.17
CVE-2021-39139 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39140 XStream can cause a Denial of Service.
CVE-2021-39141 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39144 XStream is vulnerable to a Remote Command Execution attack.
CVE-2021-39145 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39146 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39147 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39148 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39149 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39150 A Server-Side Forgery Request can be activated unmarshalling with XStream to access data streams from an arbitrary URL referencing a resource in an intranet or the local host.
CVE-2021-39151 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39152 A Server-Side Forgery Request can be activated unmarshalling with XStream to access data streams from an arbitrary URL referencing a resource in an intranet or the local host.
CVE-2021-39153 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-39154 XStream is vulnerable to an Arbitrary Code Execution attack.
Version 1.4.16
CVE-2021-29505 XStream is vulnerable to a Remote Command Execution attack.
Version 1.4.15
CVE-2021-21341 XStream can cause a Denial of Service.
CVE-2021-21342 A Server-Side Forgery Request can be activated unmarshalling with XStream to access data streams from an arbitrary URL referencing a resource in an intranet or the local host.
CVE-2021-21343 XStream is vulnerable to an Arbitrary File Deletion on the local host when unmarshalling as long as the executing process has sufficient rights.
CVE-2021-21344 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-21345 XStream is vulnerable to a Remote Command Execution attack.
CVE-2021-21346 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-21347 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-21348 XStream is vulnerable to an attack using Regular Expression for a Denial of Service (ReDos).
CVE-2021-21349 A Server-Side Forgery Request can be activated unmarshalling with XStream to access data streams from an arbitrary URL referencing a resource in an intranet or the local host.
CVE-2021-21350 XStream is vulnerable to an Arbitrary Code Execution attack.
CVE-2021-21351 XStream is vulnerable to an Arbitrary Code Execution attack.
Version 1.4.14
CVE-2020-26258 A Server-Side Forgery Request can be activated unmarshalling with XStream to access data streams from an arbitrary URL referencing a resource in an intranet or the local host.
CVE-2020-26259 XStream is vulnerable to an Arbitrary File Deletion on the local host when unmarshalling as long as the executing process has sufficient rights.
Version 1.4.13
CVE-2020-26217 XStream can be used for Remote Code Execution.
Version 1.4.9
CVE-2017-7957 XStream can cause a Denial of Service when unmarshalling void.
Version 1.4.8
CVE-2016-3674 XML External Entity (XXE) Vulnerability in XStream.
Version 1.4.6 (and 1.4.10)
CVE-2013-7285 XStream can be used for Remote Code Execution.

See workarounds for the different versions covering all the CVEs listed here.

This list contains only vulnerabilities, that could be created using the Java runtime with XStream. Vulnerabilities introduced by using additional 3rd party libraries and classes are beyond XStream's responsibility.

External Security

An active Java Security Manager can prevent actions required by XStream components or converters. The same applies to environments such as Google Application Engine. XStream tries to some extent to check the functionality of a converter before it claims to handle a type.

        

Therefore it is possible that XStream behaves differently in such an environment, because a converter suddenly no longer handles a special type or any type at all. It is essential that an application that will have to run in such an environment is tested at an early stage to prevent nasty surprises.

Implicit Security

As explained above, it is possible to inject other object instances if an attacker is able to define the data used to deserialize the Java objects, see the different CVEs. Knowing how to define such an attack is the only prerequisite.

        

All those scenarios were based on types that are delivered with the Java runtime at some version. Looking at other well-known and commonly used Java libraries libraries such as ASM, CGLIB, or Groovy, you will have to assume other scenarios for exploits as well. A class like InvokerTransformer of Apache Commons Collections has a high potential for attacks. By default XStream 1.4.18 works now with a whitelist. If you modify the default setup, it is also your responsibility to protect your clients from such vulnerabilities.

        

Note: This vulnerability is not even a special problem of XStream. XML being deserialized by XStream acts here like a script, and the scenario above can be created with any script that is executed within a Java runtime (e.g. using its JavaScript interpreter) if someone is able to manipulate it externally. The key message for application developers is that deserializing arbitrary user-supplied content is a dangerous proposition in all cases. The best approach to prevent such an attach is a whitelist, i.e. the deserialization mechanism should only allow explicit types. See also the advice for vulnerabilities using Java Serialization.

A blacklist for special classes only creates therefore a scenario for a false security, 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).

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).

Explicit Security

    

Starting with XStream 1.4.7, it is possible to define permissions for types, to check the type of an object that should be unmarshalled. Those permissions can be used to allow or deny types explicitly With these permissions it is at least not possible to inject unexpected types into an object graph. The security framework supports the setup of a blacklist or whitelist scenario. Any application should use this feature to limit the danger of arbitrary command execution if it deserializes data from an external source.

XStream itself sets up a whitelist by default, i.e. it blocks all classes except those types it has explicit converters for. Until version 1.4.17 it used a blacklist by default, i.e. it tried to block all currently known critical classes of the Java runtime. Main reason for the blacklist were compatibility, it allowed to use newer versions of XStream as drop-in replacement. However, this approach has failed. A growing list of security reports has proven, that a blacklist is inherently unsafe, apart from the fact that types of 3rd libraries were not even considered. XStream provides the ability to setup a whitelist since version 1.4.7, a version released nine years before 1.4.18. Clients who have adapted their setup and initialize the security framework are able to use newer versions again as drop-in replacement. A blacklist scenario should be avoided in general, because it provides a false sense of security.

        

Note: If a type on a whitelist contains itself other members that are handled by XStream, you will have to add those member's types to the whitelist also. There is no automatism for indirect references.

        

Separate to the XStream security framework, it has always been possible to overwrite the setupConverter method of XStream to register only the required converters.

        

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.

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:

// unmarshal element of collection 
long now = System.currentTimeMillis();
// add element here, e.g. list.add(element);
SecurityUtils.checkForCollectionDoSAttack(context, now);

XML Validation

XML itself supports input validation using a schema and a validating parser. With XStream, you can use e.g. a DOM parser for validation, but it will take some effort to ensure that the XML read and written by XStream matches the schema in first place, because XStream uses additionally own attributes. Typically you will have to write some custom converters, but it can be worth the effort depending on the use case.

Security Framework

Noted above, it might be possible that other combinations are found with the Java runtime itself, or other commonly-used Java libraries that allow a similar vulnerability like the known case using the Java Beans EventHandler. To prevent such a possibility at all, XStream version 1.4.7 and above contains a security framework, allowing application developers to define which types are allowed to be unmarshalled with XStream. Use XStream.setupDefaultSecurity() to install the default whitelist of 1.4.18 already with 1.4.7 to 1.4.10.

        

The core interface is TypePermission. The SecurityMapper will evaluate a list of registered instances for every type that will be required while unmarshalling input data. The interface has one simple method:

boolean allow(Class);
        

The XStream facade provides the following methods to register such type permissions within the SecurityMapper:

XStream.addPermission(TypePermission);
XStream.allowTypes(Class[]);
XStream.allowTypes(String[]);
XStream.allowTypesByRegExp(String[]);
XStream.allowTypesByRegExp(Pattern[]);
XStream.allowTypesByWildcard(String[]);
XStream.allowTypeHierary(Class);
XStream.denyPermission(TypePermission);
XStream.denyTypes(Class[]);
XStream.denyTypes(String[]);
XStream.denyTypesByRegExp(String[]);
XStream.denyTypesByRegExp(Pattern[]);
XStream.denyTypesByWildcard(String[]);
XStream.denyTypeHierary(Class);

The sequence of registration is essential. The most recently registered permission will be evaluated first.

        

Every TypePermission has three options to implement the allow method and make decisions on the provided type:

        

Predefined Permission Types

XStream provides some TypePermission implementations to allow any or no type at all, to allow primitive types and their counterpart, null, array types, implementations match the name of the type by regular or wildcard expression and one to invert a permission.

        

Note: The examples below are examples. Some will or might enable types that are target of a security issue from above and are highlighted as dangerous.

Permission Description Example Default
AnyTypePermission Start a blacklist and allow any type. A registration of this permission will wipe any prior one. You may use the ANY instance directly. Note, that it is now in the responsibility of the developer to deny any type that might be used for arbitrary code execution as described in the CVEs above. addPermission(AnyTypePermission.ANY); no
ArrayTypePermission Allow any array type. You may use the ARRAYS instance directly. addPermission(ArrayTypePermission.ARRAYS); yes
CGLIBProxyTypePermission Allow any CGLIB proxy type. You may use the PROXIES instance directly. addPermission(CGLIBProxyTypePermission.PROXIES); no
ExplicitTypePermission Allow types explicitly by name. allowTypes(new String[] {"java.io.File", "java.lang.ProcessBuilder"});
allowTypes(new Class[] {java.io.File.class, java.lang.ProcessBuilder.class});
java.io.File, java.nio.charset.Charset, java.util.BitSet, java.lang.Class, java.lang.Object, java.lang.StackTraceElement, java.lang.String, java.lang.StringBuffer, java.lang.StringBuilder, java.net.URI, java.net.URL, java.sql.Date, java.sql.Time, java.sql.Timestamp, java.text.DecimalFormatSymbols, java.time.Duration, java.time.Instant, java.time.LocalDate, java.time.LocalDateTime, java.time.LocalTime, java.time.MonthDay, java.time.OffsetDateTime, java.time.OffsetTime, java.time.Period, java.time.Ser, java.time.Year, java.time.YearMonth, java.time.ZonedDateTime, java.time.chrono.HijrahDate, java.time.chrono.JapaneseDate, java.time.chrono.JapaneseEra, java.time.chrono.MinguoDate, java.time.chrono.Ser, java.time.chrono.ThaiBuddhistDate, java.time.temporal.ValueRange, java.time.temporal.WeekFields, java.util.Currency, java.util.Date, java.util.Locale, java.util.regex.Pattern, java.util.UUID
InterfaceTypePermission Allow any interface type. You may use the INTERFACES instance directly. addPermission(InterfaceTypePermission.INTERFACES); yes
NoPermission Invert any other permission. Instances of this type are used by XStream in the deny methods wrapping a permission. denyPermission(permissionInstance); no
NoTypePermission Start a whitelist and allow no type. A registration of this permission will wipe any prior one. You may use the NONE instance directly. addPermission(NoTypePermission.NONE); yes
NullPermission Allow null as type. You may use the NULL instance directly. addPermission(NullPermission.NULL); yes
PrimitiveTypePermission Allow any primitive type and its boxed counterpart (excluding void). You may use the PRIMITIVES instance directly. addPermission(PrimitiveTypePermission.PRIMITIVES); yes
ProxyTypePermission Allow any Java proxy type. You may use the PROXIES instance directly. addPermission(ProxyTypePermission.PROXIES); no
RegExpTypePermission Allow any type that matches with its name a regular expression. allowTypesByRegExp(new String[]{".*\\.core\\..*", "[^$]+"});
allowTypesByRegExp(new Pattern[]{Pattern.compile(".*\\.core\\..*"), Pattern.compile("[^$]+")});
TypeHierarchyPermission Allow types of a hierarchy. allowTypeHierarchy(java.lang.Throwable.class); java.lang.Enum, java.lang.Number, java.lang.Throwable, java.lang.reflect.Member, java.nio.file.Path, java.time.Clock, java.time.ZoneId, java.time.chrono.Chronology, java.util.Calendar, java.util.Collection, java.util.Map, java.util.Map.Entry, java.util.TimeZone
WildcardTypePermission Allow any type that matches with its name a wildcard expression. allowTypesByWildcard(new String[]{"java.lang.*", "java.util.**"});

Example Code Whitelist

XStream uses now the NoTypePermission by default with an internal whitelist. You can clear out this default and/or register your own permissions to adjust the security framework (the Blog type is from the Alias Tutorial):

XStream xstream = new XStream();
// clear out existing permissions and start a whitelist
xstream.addPermission(NoTypePermission.NONE);
// allow some basics
xstream.addPermission(NullPermission.NULL);
xstream.addPermission(PrimitiveTypePermission.PRIMITIVES);
xstream.allowTypeHierarchy(Collection.class);
// allow any type from the same package
xstream.allowTypesByWildcard(new String[] {
    Blog.class.getPackage().getName()+".*"
});

You may have a further look at XStream's acceptance tests, the security framework is enabled there in general.

        

Workarounds for older XStream versions

As recommended, use XStream's security framework to implement a whitelist for the allowed types. This is possible since XStream 1.4.7 and it is the default since XStream 1.4.18.

Users of XStream 1.4.17 who insist to use XStream default blacklist - despite that clear recommendation - can add these lines to XStream's setup code:

xstream.denyTypesByWildcard(new String[]{ "sun.reflect.**", "sun.tracing.**", "com.sun.corba.**" });
xstream.denyTypesByRegExp(new String[]{ ".*\\.ws\\.client\\.sei\\..*", ".*\\$ProxyLazyValue", "com\\.sun\\.jndi\\..*Enumerat(?:ion|or)", ".*\\$URLData", ".*\\.xsltc\\.trax\\.TemplatesImpl" });

Users of XStream 1.4.16 should add these lines and additionally the lines for version 1.4.17:

xstream.denyTypesByRegExp(new String[]{ ".*\\.Lazy(?:Search)?Enumeration.*", "(?:java|sun)\\.rmi\\..*" });

Users of XStream 1.4.15 should add these lines and additionally the lines for version 1.4.16 and 1.4.17:

xstream.denyTypes(new String[]{ "sun.awt.datatransfer.DataTransferer$IndexOrderComparator", "com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator" });
xstream.denyTypesByRegExp(new String[]{ ".*\\$ServiceNameIterator", "(javax|sun.swing)\\..*LazyValue", "javafx\\.collections\\.ObservableList\\$.*", ".*\\.bcel\\..*\\.util\\.ClassLoader" });
xstream.denyTypeHierarchy(java.io.InputStream.class );
xstream.denyTypeHierarchy(java.nio.channels.Channel.class );
xstream.denyTypeHierarchy(javax.activation.DataSource.class );
xstream.denyTypeHierarchy(javax.sql.rowset.BaseRowSet.class );

Users of XStream 1.4.13 and 1.4.14 should add these lines and additionally the lines for version 1.4.15 to 1.4.17:

xstream.denyTypes(new String[]{ "javax.imageio.ImageIO$ContainsFilter" });
xstream.denyTypes(new Class[]{ java.lang.ProcessBuilder.class });

Users of XStream 1.4.7 to 1.4.12 who want to use XStream with a blacklist will have to setup such a list from scratch:

xstream.denyTypes(new String[]{ "javax.imageio.ImageIO$ContainsFilter", "sun.awt.datatransfer.DataTransferer$IndexOrderComparator", "com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator" });
xstream.denyTypes(new Class[]{ java.lang.ProcessBuilder.class, java.beans.EventHandler.class, java.lang.ProcessBuilder.class, java.lang.Void.class, void.class });
".*\\.xsltc\\.trax\\.TemplatesImpl"xstream.denyTypesByRegExp(new String[]{ ".*\\$ServiceNameIterator", "javafx\\.collections\\.ObservableList\\$.*", ".*\\.bcel\\..*\\.util\\.ClassLoader", ".*\\$GetterSetterReflection", ".*\\$LazyIterator", ".*\\$PrivilegedGetter",  ".*\\.ws\\.client\\.sei\\..*", ".*\\$ProxyLazyValue", "com\\.sun\\.jndi\\..*Enumerat(?:ion|tor)", ".*\\$URLData", ".*\\.xsltc\\.trax\\.TemplatesImpl" });
xstream.denyTypesByWildcard(new String[]{ "sun.reflect.**", "sun.tracing.**", "com.sun.corba.**" });
xstream.denyTypeHierarchy(java.io.InputStream.class);
xstream.denyTypeHierarchy(java.nio.channels.Channel.class);
xstream.denyTypeHierarchy(javax.activation.DataSource.class);
xstream.denyTypeHierarchy(javax.sql.rowset.BaseRowSet.class);

Users of XStream 1.4.6 or below can register an own converter to prevent the unmarshalling of the currently know critical types of the Java runtime. It is in fact an updated version of the workaround for CVE-2013-7285:

xstream.registerConverter(new Converter() {
  public boolean canConvert(Class type) {
    return type != null
      && (type == java.beans.EventHandler.class || type == java.lang.ProcessBuilder.class || type == java.lang.Void.class || void.class
        || type.getName().equals("javax.imageio.ImageIO$ContainsFilter") || type.getName().equals("sun.awt.datatransfer.DataTransferer$IndexOrderComparator") || type.getName().equals("com.sun.corba.se.impl.activation.ServerTableEntry") || type.getName().equals("com.sun.tools.javac.processing.JavacProcessingEnvironment$NameProcessIterator")
        || type.getName().matches("javafx\\.collections\\.ObservableList\\$.*") || type.getName().matches(".*\\$ServiceNameIterator")  || type.getName().matches(".*\\$GetterSetterReflection") || type.getName().matches(".*\\$LazyIterator") || type.getName().matches(".*\\$ProxyLazyValue") || type.getName().matches(".*\\.bcel\\..*\\.util\\.ClassLoader") || type.getName().matches(".*\\.ws\\.client\\.sei\\..*") || type.getName().matches("com\\.sun\\.jndi\\..*Enumerat(?:ion|or)")
        || type.getName().endsWith(".$URLData") || type.getName().endsWith(".xsltc.trax.TemplatesImpl")
        || type.getName().startsWith("sun.reflect.") || type.getName().startsWith("sun.tracing.") || type.getName().startsWith("com.sun.corba.")
        || java.io.InputStream.class.isAssignableFrom(type) || java.nio.channels.Channel.isAssignableFrom(type) || javax.activation.DataSource.isAssignableFrom(type) ||javax.sql.rowset.BaseRowSet.isAssignableFrom(type)
        || Proxy.isProxy(type));
  }

  public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {
    throw new ConversionException("Unsupported type due to security reasons.");
  }

  public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
    throw new ConversionException("Unsupported type due to security reasons.");
  }
}, XStream.PRIORITY_VERY_HIGH);