当前位置: 首页 > 科技观察

Java反序列化基础-JDK动态代理

时间:2023-03-11 21:58:45 科技观察

0x01Java的代理模式先说说什么是代理模式。要说代理模式,还得从代理说起。下图中的中介就是我们所说的代理。一、静态代理的简单理解静态代理以租客找中介向房东租房为例。实现租客找中介租房东,在Java中需要4个文件,分别是房源、房东、中介、租客。其中,房源应该是接口,其余三项是类。不明白方圆为什么是界面大师。这跟Java编程的设计思想有关。我个人喜欢将它与C++中的纯虚函数进行比较。静态代理可以移步狂神的视频了解。Rent.java:这是一个接口,可以抽象的理解为房源。作为一个住房源,它有一个租用房子的方法rent()。Rent.javapackagesrc.JdkProxy.StaticProxy;//租借接口publicinterfaceRent{publicvoidrent();}Host.java:这是一个类,这个类是房东,作为房东,他需要实现Rent.java接口,并实现接口的rent()方法。Host.javapackagesrc.JdkProxy.StaticProxy;publicclassHostimplementsRent{publicvoidrent(){System.out.println("房东要出租房子");}}client.java:这是一个启动类,这个类其实就是租客,租客的想法也很简单,就是找中介,然后把房子租出去(为什么不呢直接找房东?因为房东平时不想管那么多事情,房屋供应基本被中介垄断)因为租客要找中介看房而不是去找房东看房房子,所以我们这里先实现Proxy.java,即先实现中介相关的功能。Proxy.java:这是一个类。这个类是中介,也就是代理。他需要有房东的清单。但是,我们通常不继承地主,而是将地主作为私有属性宿主使用。我们使用host.rent()来实现租房的方法。Proxy.javapackagesrc.JdkProxy.StaticProxy;//中介公共类Proxy{privateHosthost;publicProxy(){}publicProxy(Hosthost){this.host=host;}publicvoidrent(){host.rent();}}Client.java租户找中介看房。packagesrc.JdkProxy.StaticProxy;//启动器publicclassClient{publicstaticvoidmain(String[]args){Hosthost=newHost();代理代理=新代理(主机);代理.rent();}}这样,房子的基本检查就完成了~不过,租房的流程是不是就结束了呢?不可能,因为中介还要收中介费?有些行为中介可以做,房东做不到,比如看房、收中介费等等。所以我们要在Proxy.java中实现这些功能。改进Proxy.javapackagesrc.JdkProxy.StaticProxy;//中介publicclassProxy{privateHosthost;publicProxy(){}publicProxy(Hosthost){this.host=host;}publicvoidrent(){host.rent();合同();票价();}//看房publicvoidseeHouse(){System.out.println("中介带你看房");}//收取中介费publicvoidfare(){System.out.println("收取中介费");}//签署租赁合同publicvoidcontract(){System.out.println("签署租赁合同");}}优点:可以让我们真实的角色更加纯粹。停止关注一些公共事物。公共业务由代理完成。分工实现。当公共业务扩展时,它变得更加集中和方便。缺点:一个真实类对应一个代理角色,代码量成倍增加,开发效率降低。我们想要静态代理的好处,不想静态代理的缺点,所以有了动态代理!深入理解静态代理深入实际业务,比如我们平时做的最多的CRUDUserService.java,就是一个接口。我们定义了四个抽象方法。packagesrc.JdkProxy.MoreStaticProxy;//深入理解静态代理publicinterfaceUserService{publicvoidadd();公共无效删除();公共无效更新();publicvoidquery();}我们需要一个真实的对象来完成这些增删改查操作。UserServiceImpl.javapackagesrc.JdkProxy.MoreStaticProxy;publicclassUserServiceImplimplementsUserService{@Overridepublicvoidadd(){System.out.println("Addedauser");}@Overridepublicvoiddelete(){System.out。println("一个用户被删除了");}@Overridepublicvoidupdate(){System.out.println("Auserwasupdated");}@Overridepublicvoidquery(){System.out.println("查询一个用户");}}需求来了,现在要加一个日志功能,怎么实现呢!思路一:在实现类中添加代码【麻烦!】思路二:使用代理来做,在不改变原有业务情况的情况下实现这个功能是最好的!处理方法:添加代理类处理日志。UserServiceProxy.javapackagesrc.JdkProxy.MoreStaticProxy;//代理publicclassUserServiceProxyimplementsUserService{privateUserServiceImpluserService;publicvoidsetUserService(UserServiceImpluserService){this.userService=userService;}publicvoidadd(){log("add");用户服务.add();}publicvoiddelete(){log("删除");userService.delete();}publicvoidupdate(){log("更新");用户服务.update();}publicvoidquery(){log("query");用户服务查询();}//添加日志方法publicvoidlog(Stringmsg){System.out.println([Debug]used"+msg+"method");}}修改启动器Client.javapackagesrc.JdkProxy.MoreStaticProxy;publicclassClient{publicstaticvoidmain(String[]args){UserServiceImpluserService=newUserServiceImpl();UserServiceProxyproxy=newUserServiceProxy();proxy.setUserService(userService);代理.add();}}这样,添加服务点的日志就成功了。2.动态代理我们前面讲过静态代理,还记得吗?每多一个房东就需要多一个中介,这显然不符合生活认知(对于租户来说,如果采用静态代理模式,每当要换一个房东,就必须再换一个中介,在开发当中,如果中间代码多,代码量会更大)动态代理的出现就是为了解决上述静态代理的不足。动态代理的一些基础知识下面主要是一些源码,大家可以不看。不推荐阅读,但为了文章内容的完整性,还是贴出来吧。动态代理的作用与静态代理相同。需要实体类、代理类和启用程序。动态代理的代理类是动态生成的,静态代理的代理类是事先写好的。JDK的动态代理需要了解两个核心类:InvocationHandler调用处理程序类和Proxy代理类InvocationHandler:调用处理程序公共接口InvocationHandlerInvocationHandler是代理实例的调用处理程序实现的接口每个代理实例都有一个关联的调用处理程序。对象调用(对象代理,方法方法,对象[]args);当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的invoke()方法。参数:proxy-调用该方法的代理实例method-在代理实例上调用接口方法的实例对应的方法。方法对象的声明类将是方法声明的接口,它可以是代理类继承方法的代理接口的超接口。args-包含传递给代理实例的方法调用参数值的对象数组,如果接口方法没有参数,则为null。原始类型的参数包装在适当的原始包装类的实例中,例如java.lang.Integer或java.lang.Boolean。Proxy:ProxypublicclassProxyextendsObjectimplementsSerializableProxy提供了创建动态代理类和实例的静态方法,它也是这些方法创建的所有动态代理类的超类。动态代理类(以下简称代理类)是在创建类时实现运行时指定的接口列表的类,具有如下所述的行为。代理接口是由代理类实现的接口。代理实例是代理类的实例。publicstaticObjectnewProxyInstance(ClassLoaderloader,class[]interfaces,InvocationHandlerh)throwsIllegalArgumentException返回指定接口的代理类的实例,该接口将方法调用分派到指定的调用处理程序。parametersloader–定义代理类的类加载器interfaces–代理类实现的接口列表h–调度方法调用的调用处理函数动态代理的代码实现编写动态代理的代码需要把握两个关键点①:我们的代理是一个接口,不是单个用户。②:代理类是动态生成的,不是静态固定的。只能说这种编程思维真的很牛逼。其实我们也可以实现任意接口的动态代理实现,这里就不贴了。首先是我们的接口类UserService.javapackagesrc.JdkProxy.DynamicProxy;公共接口UserService{publicvoidadd();公共无效删除();公共无效更新();publicvoidquery();}接下来我们需要使用实体类来实现这个抽象类UserServiceImpl.javapackagesrc.JdkProxy.DynamicProxy;publicclassUserServiceImplimplementsUserService{@Overridepublicvoidadd(){System.out.println("Addedauser");}@Overridepublicvoiddelete(){System.out.println("一个用户被删除");}@Overridepublicvoidupdate(){System.out.println("Auserwasupdated");}@Overridepublicvoidquery(){System.out.println("查询到一个用户");}}接下来是动态代理包的实现类src.JdkProxy.DynamicProxy;importjava.lang.reflect.InvocationHandler;importjava.lang.reflect.Method;importjava.lang.reflect.Proxy;publicclassUserProxyInvocationHandlerimplementsInvocationHandler{//代理接口privateUserServiceuserService;publicvoidsetUserService(UserServiceuserService){this.userService=userServ冰;}//动态生成代理类实例publicObjectgetProxy(){Objectobj=Proxy.newProxyInstance(this.getClass().getClassLoader(),userService.getClass().getInterfaces(),this);返回对象;}//处理代理类实例并返回结果@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{log(method);对象obj=method.invoke(userService,args);返回对象;}最后写我们的Client,也就是starterClient.javapackagesrc.JdkProxy.DynamicProxy;importsrc.JdkProxy.DynamicProxy.UserServiceImpl;publicclassClient{publicstaticvoidmain(String[]args){//真正作用UserServiceImpluserServiceImpl=新的UserServiceImpl();//代理角色,UserProxyInvocationHandler不存在userProxyInvocationHandler=newUserProxyInvocationHandler();userProxyInvocationHandler.setUserService((UserService)userServiceImpl);//设置被代理的对象//动态生成代理类UserServiceproxy=(UserService)userProxyInvocationHandler.getProxy();代理.add();代理.删除();代理.更新();代理.que瑞();}}如上所述,我们的动态代理已经完成0x02。动态代理在反序列化中的作用如果纯粹是为了开发是没有意义的。下面重点介绍动态代理是如何参与反序列化攻击的。回到上一篇的内容,我们之前说过要利用反序列化漏洞,需要一个入口类。我们先假设有一个类B.f可以被利用,比如Runtime.exec。我们定义入口类为A,我们的理想情况是A[O]->O.f,那么我们可以将传入的参数O替换为B。但是在实战情况下,这种情况是极其少见的。回到实战情况,比如我们的入门类A有方法O.abc,即A[O]->O.abc;而O,如果是动态代理类,O方法的invoke方法中有.f,漏洞可利用,我们展示一下。A[O]->O.abcO[O2]invoke->O2.f//此时将B替换为O2最后---->O[B]invoke->B.f//达到利用的效果动态代理在反序列化中的使用与readObject相同。反序列化时会自动执行readObject方法。invoke方法在动态代理中自动执行。0x03参考https://www.bilibili.com/video/BV1mc411h719?p=9https://www.bilibili.com/video/BV1mc411h719?p=10https://www.bilibili.com/video/BV1mc411h719?p=11https://www.bilibili.com/video/BV16h411z7o9?p=3