Java_Sec Learn
https://drun1baby.top
https://www.bilibili.com/video/BV16h411z7o9?spm_id_from=333.788.player.switch&vd_source=d51dbb41ef00391c5c021ee533eafd8e&p=2
https://github.com/Drun1baby/JavaSecurityLearning
Java 序列化
什么是序列化与反序列化
序列化: 对象 -> 字符串
反序列化: 字符串 -> 对象
为什么要进行序列化和反序列化
主要是用来传输数据
可以实现数据的持久化,通过序列化的方法可以把数据永久的保留。
可以利用序列化实现远程通信,即在网络上传送对象的字节序列。
基本反序列化实例
- Person.java
1 | package Demo1; // 修改成自己的 Package 路径 |
- SerializationTest.java
把序列化的代码封装进了 serialize 方法
FileOutputStream 输出流对象, 在调用oos的writeObject方法进行序列化操作
1 | package Demo1; |
- UnSerializationTest.java
1 | package Demo1; |
注意:
序列化类需要实现Serializable接口
静态成员变量不能被序列化 (序列化是针对对象属性的,静态成员变量属于类)
transient标识的对象成员不参与序列化
序列化安全问题
可能存在漏洞的形式
- 入口类 readObject直接调用危险方法
序列化,反序列化后就会弹计算器
1 | private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { |
- 入口参数中包含可控类,该类有危险方法,readObject时调用
- 入口类参数中包含可控类,该类又调用其他有危险方法的类,readObject时调用
- 构造函数/静态代码块等类加载时隐式执行
产生漏洞的攻击路线
攻击前提: 继承 Serializable
入口类:source (重写 readObject 调用常见的函数;参数类型宽泛,比如可以传入一个类作为参数;最好 jdk 自带)
找到入口类之后要找调用链 gadget chain 相同名称、相同类型
执行类 sink (RCE SSRF 写文件等等)比如 exec 这种函数
这里以 HashMap 为例子:
继承了 Serializable 接口
找到 readObject 方法。
看到赋值给了 key 和 value,然后丢进了 hash 这个方法里。跟进
满足 key 不为空,则 h = key.hashCode() ,继续跟进
hashCode 位置处于 Object 类当中,满足我们 调用常见的函数 这一条件。
分析 URLDNS 利用链
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/URLDNS.java
URLDNS 是 ysoserial 中一个利用链的名字,但是准确来说不能称为利用链;因为该利用链只作为探测是否存在反序列化漏洞使用。
1 | /* URLDNS 利用链介绍 |
复现一下:
URL 是由 HashMap 的 `put` 方法产生的,所以我们先跟进 `put` 方法
可以看到调用了 hash 方法,继续跟进
看到调用了 key 的 `hashCode` 方法
在这里,key 实际上就是我们传入的 URL 地址,也就是 http://1sk79f.dnslog.cn
所以我们跟进 URL 查看 hashCode 方法
看到调用的是 handler 的 `hashCode`,跟进查看
继续跟进
发现 `handler` 就是 `URLStreamHander` 抽象类中的。
我们再去找 `URLStreamHandler` 的 `hashCode` 方法。
继续跟进 getHostAddress 后发现,就是根据 URL 获取主机名,ip 地址。

至此,整条 URLDNS 链就很清楚了:
1 | HashMap -> readObject() |
但是我们在序列化代码的时候,DNSLOG 居然接收到了
为什么序列化(不进行序列化也会)的时候也会接收到 URLDNS 请求?
URL -> hashCode()
1 | public synchronized int hashCode() { |
可以看到,如果这里 code 不是 -1,就不会发起请求。
这里先穿插讲一下反射, 可以先看完反射再看下面内容
要让这里不发起请求,需要让 url 对象 code 值不是 -1
序列化(也就是为了后面反序列化发起请求)的时候,需要让这个时候的 code 值为 -1
所以要通过反射的方式去操作,修改 url -> hashCode 的值不为 -1 ,然后进行 put
1 | HashMap<URL, Integer> hashmap = new HashMap<>(); |
验证:
让 hashCode 在序列化的时候再变为 -1,再用上面那条命令修改值是否可行?
1 | f.set(url, -1); |
验证:
我们在 URL -> hashCode 处下断点,debug 反序列化测试类
可以看到此时的 hashCode 确实是 -1
反序列化后触发了了 URLDNS
注意:
如果出现:Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field private int java.net.URL.hashCode accessible: module java.base does not "opens java.net" to unnamed module @12edcd21 报错
是因为 java 版本太高导致的
添加
1 | --add-opens java.base/java.net=ALL-UNNAMED |
反射
反射的作用: 让 java 具有动态性
Java 的反射机制是指:对于任何一个类都能够知道它的所有属性和方法;并且对于任意一个类都能调用它的任意一个方法;这种动态获取信息以及调用的方法,称为 java 语言的反射。
Java 本身是一种静态语言
1 | Student student = new Student(); |
反过来,python 就是动态语言
1 | student = Student() # 只要给个名字就行,不用声明类型 |
正射与反射
- 正射:
我们在编写代码的时候,需要用到一个类的时候,都会先了解这个类是做什么的;然后实例化这个类,接着用实例化好的对象进行操作,这就是正射。
1 | Student student = new Student(); |
- 反射:
反射就是我们一开始不知道初始化的类对象是什么,自然也无法使用 new 关键词来创建对象;以创建 person 类(见上)反射作为例子讲解。
1 | public static void main(String[] args) throws Exception { |
Class 是 c 的抽象, c 是 Class 的实例;
==所以反射就是操作 Class==
反射的使用方法
获取类的方法: forName
实例化类对象的方法:newInstance
获取函数的方法:getMethod
执行函数的方法:invoke
- 实例化对象
1 | public static void main(String[] args) throws Exception { |
但是 `newInstance` 是无参方法,无法传入参数。
我们一般使用 getConstructor 的方式来获取构造方法,然后实例化对象,即可传入参数。
1 | public static void main(String[] args) throws Exception { |
getConstructor 中需要填入 Person 中构造方法的参数
- 获取类里面属性

getFields 只打印 public :
1 | Field[] personFields = c.getFields(); |
getDeclaredFields 打印所有变量:
1 | Field[] personFields = c.getDeclaredFields(); |
getField() / getDeclaredField() 相同,只不过变成了通过变量名获取
尝试修改 name:
1 | Field nameFiled = c.getDeclaredField("name"); |
nameFiled.set 需要传入两个参数
但是如果我们要去修改 private int age; ,会报错,提示 age 属性为 private:
但是反射给的权限还是很大的,只需要加上 setAccessible(true) 就可以修改,也就是暴力反射:
1 | Field nameFiled = c.getDeclaredField("age"); |
- 调用类里面的方法
Person 类中定义了 action 方法:
1 | public void action(String s){ |
获取所有方法
1 | Method[] methods = c.getMethods(); |
也可以获取单个方法:
需要注意,和上面获取构造方法一样也需要传入一个泛型, 这里是告诉 getMethod 要传入的是 String 类型的参数
1 | Method method = c.getMethod("action",String.class); |
关于私有方法也和上面修改私有属性值一样,需要修改为 Declared,并且设置访问权限
1 | Method method = c.getDeclaredMethod("action",String.class); |
反射小案例 - 反射弹 calc
正常 getRuntime , calc
1 | Runtime.getRuntime().exec("calc"); |
反射:
先获取原型类 c ,然后通过 c 获取 exec 和 getRuntime 方法,然后先创建 getRuntime 的实例,然后再去 exec 调用命令。
需要注意的点只有获取 exec 方法的时候需要跟一个参数,直接跟进 Runtime 方法查看即可。
1 | Class c = Class.forName("java.lang.Runtime"); |
当然,我们也可以进行简化,写成(构式)代码,一行完成:
1 | Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")), "C:\\WINDOWS\\System32\\calc.exe"); |
Java - IO stream
IO 是指 Input/Output,即输入和输出。以内存为中心
为什么要把数据读到内存中才能处理这些数据?因为代码是在内存中运行的,数据也必须读到内存
先讲一下关于 Java 文件的一些操作。
创建文件的方式
new File(String pathName)
1 | public class IODemo1 { |
new File(String path,File Name), 大同小异
1 | File abFile = new File("H:\\0x0B_FUXIAN\\Java\\JavaSec\\src\\IODemo"); |
获取文件信息
1 | public class IODemo2 { |
目录与文件操作
- 文件删除
file.delete() - 目录删除
file.delete()【只有空的目录可以删除成功】 - 创建单级目录
file.mkdir() - 创建多级目录
file.mkdirs()
文件流的一些操作
- 执行
whoami并输出
1 | InputStream inputStream = Runtime.getRuntime().exec("whoami").getInputStream(); |
- 读取文件内容并输出
1 | public class IODemo4 { |
这样操作英文(一个字节)字符没问题,但是中文(UTF-8)字符占三个字节,会出现乱码
要解决这一问题,需要理解原理FileInputStream 读取的是字节流到内存中。
所以我们可以继续嵌套一层 InputStreamReader 来转换为字符流,然后在输出:
1 | public class IODemo4 { |
- 文件写入也很简单,和文件读取大差不差
1 | Scanner scanner = new Scanner(System.in); |
Java 代理模式
静态代理
租房的人 -> 中介 ->(代理的方式) 房东 -> 房源
用 java 代码举例一下:
- Rent.java (房源 - 注意是 interface)
1 | public interface Rent { |
- Host.java (房东)
1 | public class Host implements Rent { |
- Proxy.java (中介
1 | public class Proxy { |
- Client.java
1 | public static void main(String[] args) { |
Client 想租房直接找 Proxy 即可
还有一些功能是中介可以做的但房东不可以,比如说收中介费
但是需要添加很多的代码量
因此就有了动态代理:
JDK 动态代理
- UserService.java 接口类
1 | public interface UserService { |
- UserServiceImpl.java 接口实现类
1 | public class UserServiceImpl implements UserService { |
- UserInovacationHandler.java 动态代理实现类
1 | public class UserInvocationHandler implements InvocationHandler { |
- ProxyTest.java
1 | public class ProxyTest { |
动态代理在反序列化中的作用
态代理在反序列化当中的利用和 readObject 是异曲同工的
readObject方法在反序列化当中会被自动执行invoke方法在动态代理当中会自动执行
如果说存在一个可以利用的类为 B.f ,比如 RunTime.exec 这种
入口类设置为 A ,最理想的情况下是 A[O] -> O.f,然后直接把 O 替换为 B 就可以执行了,但是在实战中这种情况是极少的。
但是如果入口类 A 存在 O.abc 这个方法,也就是 A[O] -> O.abc ,O 又是一个动态代理。
那 O 的 invoke 方法里存在 .f 方法就可以漏洞利用了
也就是:
1 | A[O] -> O.f = A[B] -> B.f XXX |
- Title: Java_Sec Learn
- Author: xekOnerR
- Created at : 2026-01-29 17:27:44
- Updated at : 2026-01-29 18:23:02
- Link: https://xekoner.xyz/2026/01/29/Java-sec/
- License: This work is licensed under CC BY-NC-SA 4.0.