从0到1java安全4

文章探讨了JavaEE中的反序列化过程及其可能带来的安全问题。序列化用于转换内存对象为字节流,便于数据传输和持久化存储。反序列化则反之。重写`readObject()`方法可能导致安全漏洞,例如直接调用危险方法或者通过调用链触发恶意行为。文章通过示例展示了如何利用这些漏洞,包括在`toString()`方法中插入代码以及通过可控类影响序列化过程。此外,还提到了HashMap在反序列化时可能触发的DNS请求,揭示了潜在的远程代码执行风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

知识点

1、JavaEE-反序列化-解释&使用&安全

2、JavaEE-安全-利用链&直接重写方法

3、JavaEE-安全-利用链&外部重写方法

项目案例

Java-原生使用-序列化&反序列化

Java-安全问题-重写方法&触发方法

Java-安全问题-可控其他类重写方法

一些罗列

img

序列化过程

在这里插入图片描述

1、序列化与反序列化

序列化:将内存中的对象压缩成字节流

反序列化:将字节流转化成内存中的对象

2、为什么有序列化技术

序列化与反序列化的设计就是用来传输数据的。

当两个进程进行通信的时候,可以通过序列化反序列化来进行传输。

能够实现数据的持久化,通过序列化可以把数据永久的保存在硬盘上,也可以理解为通过序列化将数据保存在文件中。

应用场景

(1) 想把内存中的对象保存到一个文件中或者是数据库当中。

(2) 用套接字在网络上传输对象。

(3) 通过RMI传输对象的时候。

3、几种创建的序列化和反序列化协议

• JAVA内置的writeObject()/readObject()

writeObject()//写 readObject()//读

• JAVA内置的XMLDecoder()/XMLEncoder

• XStream

• SnakeYaml

• FastJson

• Jackson

如果重写了readObject方法,可能不是一样的触发的链条了。

4、为什么会出现反序列化安全问题

内置原生写法分析

• 重写readObject方法

• 输出调用toString方法

5、反序列化利用链

(1) 入口类的readObject直接调用危险方法

(2) 入口参数中包含可控类,该类有危险方法,readObject时调用

(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用

(4) 构造函数/静态代码块等类加载时隐式执行

安全问题

(1) 入口类的readObject直接调用危险方法

(2) 入口参数中包含可控类,该类有危险方法,readObject时调用

(3) 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用

(4) 构造函数/静态代码块等类加载时隐式执行

原生使用-序列化&反序列化

序列化(内置原生写法分析)

新建项目UserDemo,和上边一样默认配置就行。

package com.example.serialtestdemo;
import  java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UserDemo implements Serializable {

    public String name = "xiaodi";

    public String gender = "man";
    public  Integer age = 30;

    public UserDemo(String name, String gender, Integer age) {
       this.name = name;
       this.gender = gender;
       this.age = age;
       System.out.println("UsedDemo:"+name);
       System.out.println("UsedDemo:"+gender);
       System.out.println("UsedDemo:"+age);
    }

    @Override
    public String toString() {
        return "User{"+
                "name='" + name + '\''+
                ", gender='" + gender +'\'' +
                ", age=" + age +
                '}';

    }




}

新建SeralizableDemo()

package com.example.serialtestdemo;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

 public class SerializableDemo {
    public static void main(String[] args) throws IOException {
        //创建一个对象 引用UserDemo
        UserDemo u = new UserDemo("zhangsan", "gay", 31);
        //System.out.println(userDemo);

        //调用方法,输出序列化的class
        SerializableTest(u);
    }

    public static void SerializableTest(Object obj) throws IOException {

        //FileOutputStream()输出文件

        //将obj对象序列化后写入ser.txt
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        oos.writeObject(obj);
    }
}

会生成对应的ser.txt,里面写的是序列化的内容

在这里插入图片描述

新建UnSerializableDemo,反序列化操作

package com.example.serialtestdemo;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnSerializableDemo {

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

        //调用下面的方法,传输ser.txt 解析还原返序列化
        Object obj = UnSerializableTest("ser.txt");
        System.out.println(obj);
    }

    public static Object UnSerializableTest(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object o = ois.readObject();
        return o;
    }
}

成功执行返序列化

在这里插入图片描述

整个流程:传入值-》序列化-》返序列化。

返序列化重要代码

    public static Object UnSerializableTest(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object o = ois.readObject();
        return o;
    }

跟进readobject(),是在jdk自带的readObject()方法中

文件路径(C:\Program Files\Java\jdk1.8.0_181\jre\lib\rt.jar!\java\io\ObjectInputStream.class)

在这里插入图片描述

如果说我们不用自带的jdk的readObject()整个方法,自己写一个readObject()呢?就引出了下边的问题

Java-安全问题-重写方法&触发方法

重写readObject方法

修改UserDemo

package com.example.serialtestdemo;
import  java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UserDemo implements Serializable {

    public String name = "xiaodi";

    public String gender = "man";
    public  Integer age = 30;

    public UserDemo(String name, String gender, Integer age) {
       this.name = name;
       this.gender = gender;
       this.age = age;
       System.out.println("UsedDemo:"+name);
       System.out.println("UsedDemo:"+gender);
       System.out.println("UsedDemo:"+age);
    }

    @Override
    public String toString() {
        return "User{"+
                "name='" + name + '\''+
                ", gender='" + gender +'\'' +
                ", age=" + age +
                '}';

    }

    private void readObject(ObjectInputStream ois) throws IOException{


        Runtime.getRuntime().exec("calc");
        System.out.println("成功调用重写方法readObject()");

    }


}

然后再次运行SerializableDemo,以及UnserializableDemo

在这里插入图片描述

会发现成功弹出计算器,成功调用了重写方法readObject()

(这边修改了一下UI界面,在文件-》设置-》UI设置里开启)

在这里插入图片描述

因为在UserDemo中有写readObject()方法,所以在执行的序列化的过程中去执行了自己写的的readObject()类,而不是本地环境自带的readObject()类~

跟踪代码

点击调试,让后步入进去。

在这里插入图片描述

随后执行了calc。这边问题本质是执行了自己写的readObject()方法。我们随后指向JDK自带的readObject()方法。

在这里插入图片描述

修改UserDemo的readobject()内容

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

        //指向正确ReadObject
        ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
        System.out.println("成功调用重写方法readObject()");


    }

在这里插入图片描述

再一次进行返序列化看看效果(再调试跟一次)

在这里插入图片描述

发现和之前的流程不相同了。但是同样也可以弹出计算器~

输出调用toString方法

将UserDemo的readObject()方法注释掉,在toString方法中写入calc

package com.example.serialtestdemo;
import  java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UserDemo implements Serializable {

    public String name = "xiaodi";

    public String gender = "man";
    public  Integer age = 30;

    public UserDemo(String name, String gender, Integer age) {
       this.name = name;
       this.gender = gender;
       this.age = age;
       System.out.println("UsedDemo:"+name);
       System.out.println("UsedDemo:"+gender);
       System.out.println("UsedDemo:"+age);
    }

    @Override
    public String toString() {

        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return "User{"+
                "name='" + name + '\''+
                ", gender='" + gender +'\'' +
                ", age=" + age +
                '}';
    }

    /*private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {

        //指向正确defaultReadObject
        //ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
        System.out.println("成功调用重写方法readObject()")
    }*/
}

执行UnSerializableDemo,会发现弹出了计算器

在这里插入图片描述

是因为在执行输出时,对obj对象进行输出,默认调用原始对象toString()。

如果注释时输出,计算器将不会弹出。

在这里插入图片描述

在这里插入图片描述

重写readObject()方法,执行计算器

相当于执行序列化对象里面的readObject方法,而不是本身readObject() -》 jdk1.8

如果换一个类,里面有readObject类,会不会触发呢

Java-安全问题-可控其他类重写方法

首先去DNSlog去拿到一个DNS,填入代码的URL的地方

当进行序列化时有数据进行过请求

package com.example.serialtestdemo;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.util.HashMap;
import java.net.URL;

import static com.example.serialtestdemo.SerializableDemo.SerializableTest;

public class UrlDns implements Serializable {

    public static void main(String[] args) throws IOException {
        HashMap<URL,Integer> hash = new HashMap<>();
        URL url = new URL("http://6w75dv.dnslog.cn");
        hash.put(url,1);

        SerializableTest(hash);
        

    }
}

在这里插入图片描述

当去掉序列化代码时,没有请求访问。序列化对象hash,来源自带类HashMap.(hashmap链)

Gadget Chain:
	HashMap.readObject()
		Hashmap.putVal()
			HashMap.hash()
				URL.hashCode()

ctrlf跟踪代码

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

总结

1.正常代码中,创建HashMap方法返序列化数据

2.用到原生的readObject() 方法去返序列化

readObject在ObjectInputSteams

但HashMap也有readOabject()方法

3.反序列化readObject方法调用了HashMap里面的readObject方法

执行链

Gadget Chain:
HashMap.readObject()
Hashmap.putVal()
HashMap.hash()
URL.hashCode()

最后结果触发DNS请求,如果这里可以执行命令,就是RCE

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值