0


Web安全--反序列化漏洞(java篇)

0x00 简介

序列化的意义就在于方便存储和传输,永久的保存到硬盘中,通常保存在一个文件中。

序列化:将java对象转换为字节序列的过程

反序列化:序列化的逆过程,从储存区读出字节序列还原成对象的过程

0x01 漏洞成因

java应用在对用户的输入没有进行严格的检查时,即传入了不可信的数据做反序列化操作,那么攻击者就可以传入恶意构造的输入,在进行反序列化时达到攻击者预想的效果。

0x02 实现

Java中的API实现:
位置:Java.io.ObjectOutputStream java.io.ObjectlnputStream
序列化:ObjectOutputStream类-->writeObject()
注:该方法对参数指定的obj对象进行序列化,把字节序列写到一个目标输出流中按Java的标准约定是给文件一个.ser扩展名

反序列化:ObjectInoutStream类-->readObject()
注:该方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

00 Serializable接口

将要序列化的类实现Serializable接口,表明该类是可序列化(Serializable接口是一个标记接口,不用实现任何方法。)

package test1;

import java.io.Serializable;
public class user implements Serializable{
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    private String name;
}

序列化

package test1;

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

public class UserSerializable {
    public static void main(String[] args) throws Exception {
        user user = new user();
        user.setName("biu");
        //序列化对象
        serialize(user);
    }
        public static void serialize(user user) throws Exception {
            FileOutputStream fout = new FileOutputStream("user.ser");
            ObjectOutputStream out = new ObjectOutputStream(fout);
            //利用ObjectOutputStream类的函数writeObject对对象进行序列化
            out.writeObject(user);
            out.close();
            fout.close();
            System.out.println("序列化完成.");
    }
}

序列化结果

反序列化

package test1;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileInputStream;

public class UserSerializable {
    public static void main(String[] args) throws Exception {
        user user = new user();
        user.setName("biu");
        //序列化对象
        serialize(user);
        //反序列化
        user user1 = unserialize();
        user1.setName("biubiu");
        System.out.println(user1.getName());
    }
        //模拟序列化的自定义函数
        public static void serialize(user user) throws Exception {
            FileOutputStream fout = new FileOutputStream("user.ser");
            ObjectOutputStream out = new ObjectOutputStream(fout);
            out.writeObject(user);
            out.close();
            fout.close();
            System.out.println("序列化完成.");
    }
        //模拟反序列化的自定义函数
        public static user unserialize() throws Exception{
            //读取二进制文件
            FileInputStream fileIn = new FileInputStream("user.ser");
            //new一个ObjectInputStream对象
            ObjectInputStream in = new ObjectInputStream(fileIn);
            //利用ObjectInputStream类的readObject()方法对对象反序列化
            user user = (user) in.readObject();
            in.close();
            fileIn.close();
            System.out.println("反序列化完成.");
            return user;
        }
}

反序列化结果

将二进制文件反序列化为一个新的对象,重新对name进行赋值

01 Externalizable接口

实现Externalizable接口进行序列化和反序列化,必须实现writeExternal、readExternal方法,并且还要实现一个类的无参构造方法。

package test1;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;

public class biu implements Externalizable {
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    private String name;
    //实现无参构造方法
    public biu(){
        System.out.println(this.getClass()+"无参构造方法被调用");
    }
    //实现writeExternal方法
    public void writeExternal(ObjectOutput out) throws IOException {}
    //实现readExternal方法
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {}
}

用法和Serializable接口一样,这里只展示结果

序列化结果

反序列化结果

将二进制文件反序列化为一个新的对象,重新对name进行赋值

0x03 readObject()方法

这个方法在反序列化漏洞中起到了重要的作用,因为在序列化过程中,JVM虚拟机会试图调用对象类里的 **writeObject()**和 **readObject() **方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStreamdefaultWriteObject() 方法以及 ObjectInputStreamdefaultReadObject() 方法。

实现了Serializable接口可以执行的方法包括readObject()readObjectNoData()readResolve(),以及实现了Externalizable接口的**readExternal()**方法。这些在找反序列化漏洞时都需要重点关注。

package test1;

import java.io.ObjectInputStream;
import java.io.Serializable;
public class user implements Serializable{
    public user(){
        System.out.println(this.getClass()+"无参构造方法被调用");
    }
    public user(String name){
        System.out.println(this.getClass()+"user(String name)构造方法被调用");
        this.name=name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String toString(){
        System.out.println(this.getClass() + "的toString()被调用");
        return "userclass{name="+getName()+"}";
    }
    private void readObject(ObjectInputStream in) throws Exception{
        //执行默认的readObject()方法
        in.defaultReadObject();
        System.out.println(this.getClass() + "的readObject()被调用");
        //windows重点
        Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});
    }
    private String name;
}

在readObject()方法中存在Runtime.getRuntime().exec(new String[]{"cmd", "/c", name});name为执行命令参数,那么我们可以构造一个恶意的对象,将name赋值为我们想要执行的命令,那么当反序列化时就可以触发readObject()造成RCE。

00 序列化与反序列化

package test1;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileInputStream;

public class UserSerializable {
    public static void main(String[] args) throws Exception {
        user user = new user("calc");
        user.setName("calc");
        //序列化对象
        serialize(user);
        //反序列化
        user user1 = unserialize();
        System.out.println(user1);
    }
        public static void serialize(user user) throws Exception {
            FileOutputStream fout = new FileOutputStream("user.ser");
            ObjectOutputStream out = new ObjectOutputStream(fout);
            out.writeObject(user);
            out.close();
            fout.close();
            System.out.println("序列化完成.");
    }
        public static user unserialize() throws Exception{

            FileInputStream fileIn = new FileInputStream("user.ser");
            ObjectInputStream in = new ObjectInputStream(fileIn);
            user user = (user) in.readObject();
            in.close();
            fileIn.close();
            System.out.println("反序列化完成.");
            return user;
        }
}

01 结果

class test1.useruser(String name)构造方法被调用
class test1.user的setName()被调用
序列化完成.
class test1.user的readObject()被调用
反序列化完成.
class test1.user的toString()被调用
userclass{name=calc}

可以看到在上图中不仅触发了重写的readObject()方法弹出calc程序,而且还触发了如toString()方法、setName()方法等,所以在实际寻找利用链时应该不局限于readObject()方法,其他方法中如果有执行命令的函数也可以进行利用。

当然,在实际情况中不可能会出现开发这样编写代码,这里是为了方便展示而进行编写,所以反序列化漏洞通常会需要Java的一些特性进行配合比如反射(invoke),然后就是利用链的寻找。

一般,java反序列化漏洞需要三个东西

1、反序列化入口

2、目标方法

3、利用链

0x04 补充知识点

00 下方的特征可以作为序列化的标志参考

数据以**

rO0AB

开头,基本可以确定这串就是JAVA序列化base64加密**的数据。

如果以aced开头,那么他就是这一段java序列化的16进制

01 利用工具

ysoserial是一个生成序列化payload数据的工具。

java -jar ysoserial-0.0.4-all.jar CommonsCollections1 '想要执行的命令' > payload.out

下载地址:发布 ·弗罗霍夫/伊索系列 (github.com)

参考

初探Java反序列化漏洞(一)_51CTO博客_java 反序列化

清晰易懂java反序列化漏洞简介_1024zz的博客-CSDN博客_java反序列化漏洞

标签: java 开发语言

本文转载自: https://blog.csdn.net/bobo_milk/article/details/128098492
版权归原作者 B1u_ 所有, 如有侵权,请联系我们删除。

“Web安全--反序列化漏洞(java篇)”的评论:

还没有评论