序列化反序列化介绍
反序列化类对象时有如下限制:
- 被反序列化的类必须存在。
serialVersionUID
值必须一致。
反序列化类对象是不会调用该类构造方法 需要创建一个反序列化专用的 Constructor(反射构造方法对象)
JAVA 反射机制
java反射定义
Java反射(Reflection
)是Java非常重要的动态特性,通过使用反射我们不仅可以获取到任何类的成员方法(Methods
)、成员变量(Fields
)、构造方法(Constructors
)等信息,还可以动态创建Java类实例、调用任意的类方法、修改任意的类成员变量值等。
java反射实现
- 获取 Class 对象
- 利用类对象创建对象
- 反射调用类方式
- 反射调用成员变量
- 利用反射执行代码
1. 获取 Class 对象
类名.class
Class.forName(Class_name_String)
。classLoader.loadClass(Class_name_String);
代码测试
定义一个 User.class
1 | package Test; |
定义一个调用类 Getclass.class
1 | package Test; |
运行 Getclass 的结果 得到对应的 class 对象
2. 利用类对象创建对象
获得构造函数的方法
方法 | 说明 |
---|---|
getConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的公有构造方法 |
getConstructors() | 获得该类的所有公有构造方法 |
getDeclaredConstructor(Class…<?> parameterTypes) | 获得该类中与参数类型匹配的构造方法 |
getDeclaredConstructors() | 获得该类所有构造方法 |
当我们得到一个 类对象后。我们不能直接 new 出来新对象。我们需要 getConstructor()
, getDeclaredConstructor()
函数来得到这个类对象的 构造方法。
1 | package Test; |
运行结果 已经创建了 新的 对象
对于这个 构造函数 如果这个构造函数 是 private
类型 且我们利用的 是 getDeclaredConstructor()
函数来获得构造函数。这个时候我们要在调用 newInstance
之前修改权限 加一句 constructor.setAccessible(true);
User.class
1 | package Test; |
CreateObject.class
1 | package Test; |
没有 constructor.setAccessible(true);
函数的时候
使用 constructor.setAccessible(true);
函数的时候
3. 反射调用类方法
我们要 调用我们得到的 类的方法要用到
方法 | 说明 |
---|---|
getMethod(String name, Class…<?> parameterTypes) | 获得该类某个公有的方法 |
getMethods() | 获得该类所有公有的方法 |
getDeclaredMethod(String name, Class…<?> parameterTypes) | 获得该类某个方法 |
getDeclaredMethods() | 获得该类所有方法liy |
利用我们得到我 Class 类对象,再利用 上面的函数方法得到对应的 函数方法。
实现函数
1 | package Test; |
实现
4. 反射访问变量
利用到的函数
方法 | 说明 |
---|---|
getField(String name) | 获得某个公有的属性对象 |
getFields() | 获得所有公有的属性对象 |
getDeclaredField(String name) | 获得某个属性对 |
getDeclaredFields() | 获得所有属性对象 |
AccessAttribute.class
1 | package Test; |
实现结果
5. 利用反射调用代码
在外面利用 java 利用反射来调用函数时,我们一般会用到 java.lang.Runtime
这个类
因为这个类里面有 一个 exec
函数 可以同个这个函数实现很多功能
RuntimeClass.class
1 | package Test; |
实现
反序列化机制
序列化: 将对象 转化为二进制保存
反序列化:将二进制 转化为 对象
Java 序列化是指把 Java 对象转换为字节序列的过程
ObjectOutputStream类的 writeObject() 方法可以实现序列化
Java 反序列化是指把字节序列恢复为 Java 对象的过程
ObjectInputStream 类的 readObject() 方法用于反序列化。
实现java.io.Serializable接口才可被反序列化,而且所有属性必须是可序列化的
测试
代码
1 | package sec; |
结果
在利用 JAVA 反序列化的时候。我们通过利用 反序列化+反射调用 来实现 getshell 或者调用其他函数。
通过CTF 学习 JAVA 反序列
[V&N2020 公开赛]EasySpringMVC(java反序列化)
给了一个 war 文件包,改后缀为 zip 解压得到文件夹。
利用 IDEA 查看 class 文件中的代码。
找到关键的 readObject
Tool.class
1 | // |
在 Tool.class 中的 parse 函数中有一处 反序列化操作。
1 | public static Object parse(byte[] bytes) throws Exception { |
这里是对我们传入的 bytes 进行一个反序列化
而且在我们反序列化后对其对象直接进行了 .start() 函数 –> 这就是一个后门 不用够着 反射调用,可以直接利用 反序列化执行。
所以可以直接够着 弹shell 的函数。
然后我们发现 Tool.parse 是在 ClentInfoFilter.class 中被调用了
1 | if (!b64.equals("") && bytes != null) { |
这个 bytes 对应的应该 decode base64 之后的 cookies 的值。然后调用了
Tools.parse 对其进行反序列化。然后在 readObject 函数里面进行调用
所以我们自己重写 Tools.class 让他序列化 一个 反弹shell 的payload
这样我们就能在 反序列化后 直接调用得到 shell
Main.class
1 | package com.tools; |
Tools.class
1 | package com.tools; |
buu 靶场只能访问内网
配置 frp 然后利用 ssh链接 linux Labs 然后shell反弹到 对应内网靶机
配置 frpc.ini
1 | [common] |
然后 运行 frp
1 | ./frpc -c ./frpc.ini |
然后可以进行 ssh 链接linux labs靶机
然后我们就能 ifconfig 查看 内网ip 写payload
1 | ifconfig # 查看内网ip |
bp 抓包
修改 cinfo 然后 go
得到 反弹shell
在Java 反序列化中,我们需要让 对应的 package 包路径相同,比如这个文件路径为 com.tools 所以我们创建一样的 目录。