Java安全基础知识
语雀不充钱出不了网,纯纯跳板,不定时更新。
反射
概念
Java反射机制指的是:
- 可以创建任意类的对象
- 可以获取任意对象所属类
- 可以访问任意类的,任意函数和成员
在Java安全里,我们通常利用这个来控制一些对象的成员、执行一些方法。
获取Class对象
获取Class对象通常是反射的第一步,class对象可以看作“类对象”,至于是哪个类,却决于你创建这个class对象用的哪个类。
- 类名.class
- 对象.getClass()
- Class.forName(全类名)
- ClassLoader.getSystemClassLoader().loadClass(全类名)
Class对象的方法
利用Class对象的内置方法,可以访问到一个类的各种部分。
Class clazz = Class.forName("com.example.demo.reflectdemo.UserInfo");
方法名 | 含义 | 运行结果 |
---|---|---|
clazz.getPackage() | 获取clazz所属类的包名 | package com.example.demo.reflectdemo |
clazz.getDeclaredAnnotations() | 获取clazz所属类的所有注解 | |
clazz.getModifiers() | 获取clazz所属类的修饰符 | public字符串 |
clazz.getName() | 获取clazz所属类的全类名 | com.example.demo.reflectdemo.UserInfo |
clazz.getSimpleName() | 获取clazz所属类的简单类名 | UserInfo |
clazz.getGenericSuperclass() | 获取clazz所属类的超类 | class java.lang.Object |
clazz.getGenericInterfaces() | 获取clazz所属类的实现接口 | null |
clazz.newInstance() | 创建clazz所属类的实例 | 得到userInfo对象 |
clazz.getField("age") | 获取clazz所属类/父类的Public的age属性 | public int xx.xx.UserInfo.age |
clazz.getFields() | 获取clazz所属类/父类所有Public的属性 | 略 |
clazz.getDeclaredField("age") | 获取clazz所属类的age属性,即使私有 | private int xx.xx.UserInfo.age |
clazz.getDeclaredFields() | 获取clazz所属类的所有属性,即使私有 | 略 |
clazz.getMethod("setAge",String.class) | 获取clazz所属类/父类的public的setAge方法 | public void xx.xx.UserInfo.setName(java.lang.String) |
clazz.getMethods() | 获取clazz所属类/父类的public的所有方法 | 略 |
clazz.getDeclaredMethod("getName") | 获取clazz所属类的getName方法,即使私有 | public String xx.xx.UserInfo.getName() |
clazz.getDeclaredMethods() | 获取clazz所属类的所有方法,即使私有 | 略 |
method.getParameters() | 获取传入method所属函数的所有参数 | java.lang.String arg0 |
clazz.getConstructor(String.class,int.class) | 获取一个声明为 public 构造函数实例 | public xx.xx.UserInfo(java.lang.String,int) |
clazz.getConstructors() | 获取所有声明为 public 构造函数实例 | 略 |
clazz.getDeclaredConstructor(String.class) | 获取一个声明的构造函数实例,即使私有 | private xx.xx.UserInfo(java.lang.String) |
clazz.getDeclaredConstructors() | 获取所有声明的构造函数实例,即使私有 | 略 |
Object user = c1.newInstance("Jasper",22) | 利用构造函数实例创建UserInfo实例 | 获得一个user对象,name=jasper,age=22 |
clazz.getModifiers() | 获取clazz所属类的修饰符的值 | 1(public) |
clazz.getDeclaredField("name").getModifiers() | 获取属性的修饰符的值 | 2(private) |
clazz.getDeclaredMethod("setName",String.class).getModifiers() | 获取setName方法的修饰符的值 | 1(public) |
Modifier.toString(1) | 根据修饰符的值获取对应字符串 | public(1) |
method.invoke(object, args) | 执行object的method方法,传入args参数 | 等价于object.method(args) |
反射实现命令执行
java.lang.Runtime
java.lang.Runtime这个类的构造函数是私有的,所以不能直接newInstance()创建出一个runtime实例。
利用反射执行java.lang.Runtime.getRuntime().exec("calc");
的有下面两种
1.通过getMethod
2.通过getDeclaredMethod
java.lang.ProcessBuilder
利用反射执行Process cmd = new ProcessBuilder(command).start();
的方法如下:
java.lang.ProcessImpl
这个类的命令执行就是反射实现的,代码如下:
反序列化
思想和php的序列化、反序列化一样,都是对象->字符->对象
注意几个点:
- 要序列化的类,需要实现java.io.Serializable接口
- 要序列化的类,里面的属性必须都可以序列化,不可以的需要声明是短暂的
- 反序列化的方法是可以在类里自定义的
动态加载
参考链接
全文参考自:https://zhuanlan.zhihu.com/p/567962697
动调和深入理解参考自:https://space.bilibili.com/2142877265/
简介
Java的类加载,大概就是把编译好的*.class文件给加载到内存的过程。
我们主要关注其中,会引起代码执行的部分:
- 类初始化时,执行静态代码块和静态方法
- 类使用(实例化)时,执行构造代码块和构造方法
类加载不同阶段执行的代码
Student类里写好静态代码块、静态方法、构造代码块和构造方法
类的加载与初始化
从上面可以看出执行类的初始化操作,只会调用静态代码块。
这里仅仅是进行了类的加载,并不是初始化,所有什么都没调用。
这里用Class.forName()加载类,会调用静态代码块,跟进看一下
发现多个重写的方法forName(),这里第二个参数可选是否初始化,设置成false
未执行静态代码块,说明只执行了类的加载操作。
类的实例化
显然实例化需要先初始化,所以要执行静态代码块,然后执行自己的构造代码块。
小总结
- 类的加载,执不执行代码,看实不实现初始化
- 类的初始化,执行静态代码块
- 类的实例化,执行构造代码块
类加载器
分类
一般来说分成bootstrap和非bootstrap两类加载器,其中bootstrap ClassLoader是比较底层的,还存在加载类的白名单。
非bootstrap主要又可以分成Extension ClassLoader、Application ClassLoader、User ClassLoader三类。
- Bootstrap ClassLoader:加载<JAVA_HOME>/lib下的类
- Extension ClassLoader:加载<JAVA_HOME>/lib/ext下的类
- Application ClassLoader:加载程序员自定义的类classpath/
- User ClassLoader:加载任意来源的类
双亲委派
当一个类加载器要加载类时,会让它的逻辑父亲去加载,层层上传,只有当父亲对应的目录下,找不到这个类对应的字节码文件,才让儿子去加载。
每个类加载器都有属于自己的命名空间,不同的类加载器加载同一个类,却会生成不同的对象,使用双亲委派逻辑就不会出现这个问题,一个类名只会被一个加载器加载
ClassLoader的运行过程
继承关系如下:
调试一下加载类加载器加载类的代码:
首先,因为AppClassLoader里没有一个参数的loadClass(),会走到它的父类抽象类ClassLoader里,然后调用俩参数的loadClass()
接着走进AppClassLoader的loadClass()
然后是双亲委派的逻辑,调用父亲的loadClass()重复查询
在父亲这找到了,层层返回class对象
函数调用顺序:
loadClass() -> findClass() -> defineClass()
任意类加载
创建一个Calc类用来弹计算器,编译成Calc.class
URLClassLoader.loadClass()
ClassLoader.defineClass()
这种方法的优点是不用出网,直接把class文件塞过去一股脑读出来就好。
问题是defineClass()是protected方法,在反序列化的场景不能直接反射调用。
Unsafe.defineClass()
这玩意有安全检测,不能直接实例化得到对象,是单例模式(类似Runtime)
好在他类里有现成的属性,存了unsafe对象,叫theUnsafe可以直接拿,
但是theUnsafe又是private的,所以要设置访问权限
小总结
- URLClassLoader.loadClass(),可以用不同协议实现任意类加载
- ClassLoader.defineClass(),通过字节码文件来加载类,方法protected
- Unsafe.defineClass(),同样用字节码文件来加载类,但类不能直接实例化
动态代理
参考链接
https://darkless.cn/2021/10/28/java-sec-proxy/
https://xz.aliyun.com/t/9197
https://www.bilibili.com/video/BV16h411z7o9/?p=3
概念
类似AOP切面编程,想要强化某个方法,但是又不想改变这个方法;或者是提取重复功能代码,降低复杂度。
静态代理
- 委托类和代理类都实现同一个接口
- 代理类里传入委托类对象,重写接口的的方法,在里面加要加的逻辑,并调用委托类对象.接口方法()
动态代理
静态代理不够灵活,于是有了动态代理。
下面这个类实现了InvocationHandler接口,增强函数的逻辑就在这个类的invoke()方法里面。
invoke()的特性是:后面创建的代理对象rentProxy调用每个方法都会走进这个invoke()
这里创建rentProxy代理对象的语句基本是固定写法,不用怎么研究。
安全相关
理想状况下,我们希望能找到这样的gadgets链:
然而实际情况可能是这样的:
磁石啊,很多人可能以为女神拒绝了自己,实则不然;假设a是个动态代理对象,而a对应的handler实现类里所重写的invoke()方法里又有危险函数,那么就又可以利用了
__EOF__

本文链接:https://www.cnblogs.com/jasper-sec/p/17615667.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)