Mar 20, 2020

Liferay Portal JSON Web Service RCE Vulnerabilities

This was originally posted on blogger here.

Code White has found multiple critical rated JSON deserialization vulnerabilities affecting the Liferay Portal versions 6.1, 6.2, 7.0, 7.1, and 7.2. They allow unauthenticated remote code execution via the JSON web services API. Fixed Liferay Portal versions are 6.2 GA6, 7.0 GA7, 7.1 GA4, and 7.2 GA2.

The corresponding vulnerabilities are:

CST-7111: RCE via JSON deserialization (LPS-88051/LPE-1659811 )

The JSONDeserializer of Flexjson allows the instantiation of arbitrary classes and the invocation of arbitrary setter methods.

CST-7205: Unauthenticated Remote code execution via JSONWS (LPS-97029/CVE-2020-7961)

The JSONWebServiceActionParametersMap of Liferay Portal allows the instantiation of arbitrary classes and invocation of arbitrary setter methods.

Both allow the instantiation of an arbitrary class via its parameter-less constructor and the invocation of setter methods similar to the JavaBeans convention. This allows unauthenticated remote code execution via various publicly known gadgets.

Liferay released the patched versions 6.2 GA6 (6.2.5), 7.0 GA7 (7.0.6) and 7.1 GA4 (7.1.3) to address the issues; the version 7.2 GA2 (7.2.1) was already released in November 2019. For 6.1, there is only a fixpack available.


Liferay Portal is one of, if not even the most popular portal implementation as per Java Portlet Specification JSR-168. It provides a comprehensive JSON web service API at ‘/api/jsonws’ with examples for three different ways of invoking the web service method:

  1. Via the generic URL /api/jsonws/invoke where the service method and its arguments get transmitted via POST, either as a JSON object or via form-based parameters (the JavaScript Example)
  2. Via the service method specific URL like /api/jsonws/service-class-name/service-method-name where the arguments are passed via form-based POST parameters (the curl Example)
  3. Via the service method specific URL like /api/jsonws/service-class-name/service-method-name where the arguments are also passed in the URL like /api/jsonws/service-class-name/service-method-name/arg1/val1/arg2/val2/… (the URL Example)

Authentication and authorization checks are implemented within the invoked service methods themselves while the processing of the request and thus the JSON deserialization happens before. However, the JSON web service API can also be configured to deny unauthenticated access.

First, we will take a quick look at LPS-88051, a vulnerability/insecure feature in the JSON deserializer itself. Then we will walk through LPS-97029 that also utilizes a feature of the JSON deserializer but is a vulnerability in Liferay Portal itself.

CST-7111: Flexjson’s JSONDeserializer

In Liferay Portal 6.1 and 6.2, the Flexjson library is used for serializing and deserializing data. It supports object binding that will use setter methods of the objects instanciated for any class with a parameter-less constructor. The specification of the class is made with the class object key:

{"class":"fully.qualified.ClassName", ... }

This vulnerability was reported in December 2018 and has been fixed in the Enterprise Edition with 6.1 EE GA3 fixpack 71 and 6.2 EE GA2 fixpack 169 2 and also the 6.2 GA6.

CST-7205: Jodd’s JsonParser + Liferay Portal’s JSONWebServiceActionParametersMap

In Liferay Portal 7, the Flexjson library is replaced by the Jodd Json library that does not support specifying the class to deserialize within the JSON data itself. Instead, only the type of the root object can be specified, and it has to be explicitly provided by a java.lang.Class object instance. When looking for the call hierarchy of write access to the rootType field, the following unveils:

While most of the calls have hard-coded types specified, there is one that is variable (see selected call on the right above). Tracing that parameterType variable through the call hierarchy backwards shows that it originates from a ClassLoader.loadClass(String) call with a parameter value originating from an JSONWebServiceActionParameters instance. That object holds the parameters passed in the web service call. The JSONWebServiceActionParameters object has an instance of a JSONWebServiceActionParametersMap that has a _parameterTypes field for mapping parameters to types. That map is used to look up the class for deserialization during preparation of the parameters for invoking the web service method in JSONWebServiceActionImpl._prepareParameters(Class<?>).

The _parameterTypes map gets filled by the JSONWebServiceActionParametersMap.put(String, Object) method:

/*     */   public Object put(String key, Object value)
/*     */   {
/*  64 */     int pos = key.indexOf(':');
/*     */
/*  66 */     if (key.startsWith("-")) {
/*     */       // [...]
/*     */     }
/*  71 */     else if (key.startsWith("+")) {
/*     */       // [...]
/*     */     }
/* 101 */     else if (pos != -1) {
/* 102 */       String typeName = key.substring(pos + 1);
/*     */
/* 104 */       key = key.substring(0, pos);
/*     */
/* 106 */       if (_parameterTypes == null) {
/* 107 */         _parameterTypes = new HashMap();
/*     */       }
/*     */
/* 110 */       _parameterTypes.put(key, typeName);
/*     */
/* 112 */       if (Validator.isNull(GetterUtil.getString(value))) {
/* 113 */         value = Void.TYPE;
/*     */       }
/*     */     }
/*     */
/*     */     // [...]
/*     */
/* 142 */     return super.put(key, value);
/*     */   }

Here the lines 102 to 110 are interesting: the typeName is taken from the key string passed in. So if a request parameter name contains a ‘:’, the part after it specifies the parameter’s type, i. e.:


This syntax is also mentioned in some examples in the Invoking JSON Web Services tutorial.

Later in JSONWebServiceActionImpl._prepareParameters(Class<?>), the ReflectUtil.isTypeOf(Class, Class) is used to check whether the specified type extends the type of the corresponding parameter of the method to be invoked. Since there are service methods with java.lang.Object parameters, any type can be specified.

This vulnerability was reported in June 2019 and has been fixed this in 6.2 GA6, 7.0 GA7, 7.1 GA4, and 7.2 GA2 by using a whitelist of allowed classes.


[1] There are two editions of the Liferay Portal: the Community Edition (CE) and the Enterprise Edition (EE). The CE is free and its source code is available at GitHub. Both editions have their own project and issue tracker at CE has LPS-* and EE has LPE-*. LPS-88051 was created confidentially by Code White for CE and LPE-16598 was created publicly three days later for EE.

[2] Fixpacks are only available for the Enterprise Edition (EE) and not for the Community Edition (CE).