java反序列化以及一些前置知识
最近学习了java反序列化以及反射 的相关知识,就在这稍微做一些总结。
一、Java反射
java反射其实也就是比较基础简单的,主要就是集中在一些函数上。
首先就是反射的作用是什么
1、让java具有动态性
2、修改已有对象的属性的值
3、动态生成对象
4、动态调用方法
5、操作内部类和私有方法私有变量
其次就是最主要的一点
Java反射在Java反序列化中的应用
1、定制需要的对象
2、通过invoke去调用同名函数以外的函数
3、通过Class类创建对象,引入不能序列化的类然后通过反射去执行
首先就是介绍总结一下java反射中用到的一些相关方法,当然也不是直接对着方法名记忆,那样太枯燥了,主要就是在使用的过程中去学习。
1 | Class clazz = Class.forName("java.lang.Runtime"); |
当然这样直接去运行时会产生报错的!!!这又是为什么呢?
这里主要就是因为我们无法通过newInstance去获得类的对象,而这里是因为Runtime的构造方法是私有的,是一种”单例”的设计模式
单例的设计模式:主要是考虑到某些类一般只需要类的初始化时使用一次构造方法,而不是每次都需要去再重新使用构造方法,但这样的话我们又应该怎么样去调用构造方法呢?所以设计者就设计了一个静态方法,像这里的就是getRuntime这个方法,使用这个方法就会返回一个对象
所以修改之后的代码就是
1 | class clazz = class.forName("java.lang.Runtime"); |
这样就可以去调用到我们需要的方法了。
当然这样也是还有点缺点的,就比如如果没有这个getRuntime的方法呢?这又应该怎么办?
这个时候就可以使用getConstructor这个方法去获取到我们想要的类。
格式: 类.getConstructor(构造方法的参数类型)
eg.
1 | class clazz = Class.forName("java.lang.ProcessBuilder"); |
然后就是如果构造方法是私有方法,我们应该去使用getDeclaredMethod这个方法。
getMethod 系列方法获取的是当前类中所有公共方法,包括从父类继承的方法
getDeclaredMethod 系列方法获取的是当前类中“声明”的方法,是实在写在这个类里的,包括私 有的方法,但从父类里继承来的就不包含了
还有就是getConstructor和getDeclaredConstructor基本上相似的,这里就不过多解释了
1 | class clazz = Class.forName("java.lang.Runtime"); |
setAccessible必须要设置为true,不然无法去使用。
二、JDK动态代理
首先就是个人感觉java的动态代理技术就有点与Python中的装饰器
通过调用jdk自带的相关方法,从而去省略跳过自身创建静态代理
ProxyTest.java
1 | UserInvocationHandler userinvocationhandler = new UserInvocationHandler(); |
UserInvocationHandler.java
1 | IUser user; |
这样也就成功实现了简单的动态代理了
三、类的动态加载
1 | ClassLoader cl = ClassLoader.getSystemClassLoader(); |
获得系统当前的类加载器
构造代码块,静态代码块———————无论调用什么构造方法都会先调用构造代码块。同理,静态代码块也是如此
1 | //构造代码块 |
然后就去实现一个具体的代码
1 | byte[] code = Files.readAllBytes(Paths.get("E:\\Test.class")); |
通过类加载器可以去实现加载远程或者本地的其他目录下的类
四、RMI
RMI全称是Remote Method Invocation,远程⽅法调⽤。听这名字应该也就知道,就是去从调用一个远程主机上的java方法,在这里就挑一些重点的代码片段进行讲解。
1 | LocateRegistry.createRegistry(1099); |
首先第一行这里就是创建并且执行Registry服务,这个服务就是相当于一个中继器,我将类和一个名字绑定丢到这里面去,别的人就可以通过名字去拿到这个对应的类,这就是Registry所起到的一个作用。然后就是第二行,就是将本机的一个类给绑定到了一个Registry服务上,这样等之后就可以去直接拿到这个类了。
1 | RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld)Naming.lookup("rmi://vps_ip:1099/Hello");(本机运行) |
这里就通过Naming的lookup方法去寻找这个rmi类,我们就可以在Registry中拿到我们想要的类。
codebase的利用方法
在以前的有段时间,java是可以运行在浏览器上的,就有一个codebase属性,这是一个地址,去告诉哪个地方寻找类,这个时候我们就可以使用rmi的相关操作,去使其加载我们自己部署的服务器上的一些恶意类。
就比如在log4j这个CVE中,也是可以去使用rmi去实现的,去完成反弹shell从而获得权限。
🌟补充:
🌟关于rmi服务的具体流程调试
参考文章和视频(我认为讲的特别好,强烈推荐!)
1、远程对象的创建过程
主要的作用点就在远程方法所继承的UnicastRemoteObject类中,是用于将本地方法发布的
然后就是由于端口是未知的,端口为零,然后调用exportObject方法区将方法给发布,exportObject就是发布的重要函数,继续跟进,就会发现是在一层层去调用不同类中的exportObject函数
最后在这里,会发现一个createProxy,看名字就能看出来,是一个创建代理的方法,我们跟进看看
发现是一个判断是否有_stub后缀的方法,如果没有,就创建一个,后边就是一些不太好理解的东西了
然后就跳到了这里,我们跟进函数,发现也是一个判断_Skel后缀的方法,在继续调试,发现创建了一个Target,一个很关键的东西
里面把所有相关的stud,ID等都记录进去了,然后就又是一系列的exportObject去将Target给发布出去了。
最后发现服务端还会建一张表,去将Target给存入其中,从而保留让服务端知道有关这个远程方法的所有信息。
这差不多 就是远程方法的创建的过程了
2、注册中心的创建
相比来说注册中心的创建就简单很多了,我们直接调试跟进
在这里就发现了重点—–LiveRef
在上面之前忘记将了,LiveRef又是什么,这是一个在rmi远程方法调用中非常重要的一个,他其中封装了服务端中所有相关的信息,比如远程方法的ID,地址和端口,以及各种信息
然后就发现是调用TCPEndpoint,去获取本地的Endpoint服务,从而以此去完成相关TCP服务的操作。
发现就又是exportObject方法,发现后边就是和之前的远程方法调用基本就是一致的了,就能够知道注册中心的创建,其实也是一个远程方法的发布过程实际上。
3、远程方法绑定
这里就比较简单了,因为这里并不是远程绑定,就直接调用了RegistryImpl类,把名字和远程对象放到一个叫bindings的HashTable里面。
到这里服务端的流程就走完了。
三、serialize🌟
这是学习的重难点,我现在也还知识初步了解一些,还没完全弄完😭
一般web手初识反序列化都是从php开始,首先就java的反序列化和php的还是有很大区别的,php是直接利用serialize和unserialize这两个函数进行序列化和反序列化的过程,我们无法去控制这个过程中的任何东西,而java就不一样了。
- writeObject:序列化
- readObject:反序列化
这两个主要是java中序列化反序列化所需要使用到的,一般需要搭配一些其他的东西去使用。
eg
1 | public void serialize() throws IOException |
这就是java反序列话的主要过程,是可以自己去操作序列化反序列化的一些过程的,就比如在序列化后的文件中添加一些数据一类的
这也不是唯一一种,我们也可以用byte流的方法代替文件流
1 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); |