0x0、基础 代理的使用场景:某程序员入职公司接手了一个项目,他读源码发现某些地方可以增强,比如在某些函数执行前应该打印日志。如果他直接在原始代码的基础上直接修改容易出错,他的做法是:自己实现一个类,和原始类实现相同的接口(或者继承相同的类),通过在方法中引用老程序的方法来实现自己的方法,从而实现在不改动源代码的基础上达到增强方法的目的。
代理模式:为其他对象提供一种代理以便控制对这个对象的访问(所谓控制,就是可以在其调用行为前后分别加入一些操作)。
代理模式分类:
静态代理,其实质是类的继承或接口的实现,比较容易理解,注意结合场景。
动态代理(Jdk动态代理),这是我们需要关注的重点,在反序列化漏洞的场景中需要用到!
cglib动态代理
0x1、静态代理demo和理解 ``
<code><strong>package 反序列化3; /* * 代理模式的简单demo,静态代理 * * 代理的使用场景:某程序员入职公司接手了一个项目,他读源码发现某些地方可以增强(比如在某些函数执行前应该打印日志)。 * 如果他直接在原始代码的基础上直接修改容易出错,他的做法是:自己实现一个类,和原始类实现相同的接口(或者继承相同的类), * 通过在方法中引用老程序的方法来实现自己的方法,从而实现增强方法的目的。 */ public class proxyTest { public static void main(String[] args) { //Subject sub = new RealSubject();//场景中得旧代码,老程序员写的。 Subject sub = new ProxySubject();//新入职的程序员,自己实现了ProxySubject类,然后改成了这句。来增强老程序的代码。 sub.request(); } } abstract class Subject//也可以是接口interface {//抽象角色:通过接口或抽象类声明真实角色实现的业务方法。 //类比网络代理,比如http代理,都支持http协议 abstract void request(); } class RealSubject extends Subject {//真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用。 //类比真实的http请求 public RealSubject()//默认构造方法 { } @Override public void request() { System.out.println("From real subject."); } } class ProxySubject extends Subject//关键是类的继承。 {//代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作。 //类比通过代理发出http请求,这个代理当然可以对http请求做出任何想要的修改。 private RealSubject realSubject; //以真实角色作为代理角色的属性 public ProxySubject() { } @Override public void request() //该方法封装了真实对象的request方法,老程序员的方法。 {//所谓的“控制”就体现在这里 preRequest(); if( realSubject == null ) { realSubject = new RealSubject(); } realSubject.request(); //此处执行真实对象的request方法 postRequest(); } private void preRequest() { //在请求前做某些处理,比如打印日志,修改请求包等等 System.out.println("Do something before requesting: print log,change request"); } private void postRequest() { //在请求后做某些处理,打印日志 System.out.println("Do something after requesting: print log"); } }</strong></code> **运行演示:**
****
0x2、动态代理demo及理解 但是静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。比如为老程序员的所有代码都加上日志打印,难道老程序员实现过的所有类,新程序员都需要再实现一遍吗?
所以引入动态代理来解决此类问题。JDK内置的Proxy动态代理可以在运行时动态生成字节码,而没必要针对每个类编写代理类。他只需实现一个InvocationHandler就可以了。
在java的动态代理机制中,有两个重要的类或接口:一个是 InvocationHandler(Interface);另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
package 反序列化3; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /* * 动态代理的简单demo,动态代理利用了反射机制 * 每一个动态代理类都会有一个与之关联的invocation handler。真正的调用是在invocation handler的invoke()方法里完成的。 * 感谢蝶离飞、廖新喜2位师傅的指导 */ public class proxyTest2{ public static void main(String[] args) { DynamicSubject sub=new RealDynamicSubject();//之前这里sub的类型是RealDynamicSubject,不对;但是为什么呢? Handler handler = new Handler(sub); //newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) //CLassLoader loader:指定动态代理类的类加载器,即生成完成后的代理对象的类加载器 //Class<?> interfaces:指定动态代理类需要实现的所有接口,需要被增强的接口列表(数据) //InvocationHandler h: 指定与动态代理类关联的 InvocationHandler对象,具体的增强逻辑 DynamicSubject sub2 = (DynamicSubject)Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), new Class[]{DynamicSubject.class}, handler); DynamicSubject sub3 = (DynamicSubject)Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), sub.getClass().getInterfaces(), handler); DynamicSubject sub4 = (DynamicSubject)Proxy.newProxyInstance(DynamicSubject.class.getClassLoader(), RealDynamicSubject.class.getInterfaces(), handler); //从上面的调用方法可知,可以对不同的对象使用相同的模式实现来实现其代理,这就是相对静态代理的优势。 System.out.println("sub.getClass() = "+sub.getClass()); System.out.println("DynamicSubject.class = " +DynamicSubject.class); System.out.println(new Class[]{DynamicSubject.class}); System.out.println(RealDynamicSubject.class.getInterfaces()); sub2.request(); sub3.request(); sub4.request(); } } interface DynamicSubject {//抽象角色:通过接口或抽象类声明真实角色实现的业务方法。注意:动态代理只能是接口,否则代理类转成该类型事会报错 //类比网络代理,比如http代理,都支持http协议 abstract void request(); } class RealDynamicSubject implements DynamicSubject {//真实角色:实现抽象角色,定义真实角色所要实现的业务逻辑,供代理handler处理调用。 //类比真实的http请求 public RealDynamicSubject() { } public void request() { System.out.println("From real subject."); } } /** * 处理器 */ class Handler implements InvocationHandler{ private Object obj; //被代理的对象(也就是老程序员实现的对象),不管对象是什么类型;之前声明成RealDynamicSubject,不应该这么做 /** * 所有的流程控制都在invoke方法中 * proxy:代理类 * method:正在调用的方法,反射机制调用函数所必须! * args:被调用方法的参数列表,反射机制调用函数所必须! */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {//接口必须实现的方法,也是逻辑核心 System.out.println("Do something before requesting: print log"); Object xxx = method.invoke(this.obj, args);//通过反射机制调用老程序员的对象代码。 System.out.println("Do something after requesting: print log"); return xxx; } public Handler(Object obj) { //构造函数,把真实角色的实例传递进来,这个代理handler的目的就是增强它,或者说需要调用它来实现主要的功能。 this.obj = obj; } }
运行结果:
0x3、思考总结 在后续将要学习的反序列化PoC构造过程中,我们需要用到这个动态代理机制,因为它提供一种【方法之间的跳转,从任意方法到invoke方法的跳转】,是我们将参数入口和代码执行联系起来的关键!
本文代码下载地址:
https://github.com/bit4woo/Java_deserialize_vuln_lab/tree/master/src/Step3
参考
http://www.runoob.com/design-pattern/design-pattern-intro.html
https://www.cnblogs.com/xiaoluo501395377/p/3383130.html
https://www.cnblogs.com/xiaoxi/p/5961093.html