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-165981)
- 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.
Introduction
Liferay Portal is one of the, 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:
- 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) - 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) - 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:
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 1692 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:
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 of the 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.
Demo
- [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 issues.liferay.com: CE has
LPS-*
and EE hasLPE-*
. 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).