Java动态代理模式

0x00 前言

基础知识决定上层建筑,我计划是先把JavaWeb RCE漏洞相关的基础概念都总结一遍后,再开始复现学习一些RCE的漏洞文章。

0x01 代理模式

在学习Java动态代理模式之前,先了解一下什么是代理模式。

代理模式是一种结构型设计模式, 让你能够提供对象的替代品或其占位符。 代理控制着对于原对象的访问, 并允许在将请求提交给对象前后进行一些处理。

它的好处就是如果需要在类的主要业务逻辑前后执行一些工作, 你无需修改类就能完成这项工作。 由于代理实现的接口与原类相同, 因此你可将其传递给任何一个使用实际服务对象的客户端。

代理模式角色分为 3 种:

Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,也是代理类代理真实主题的方法;

RealSubject(真实主题角色):真正实现业务逻辑的类;

Proxy(代理主题角色):用来代理和封装真实主题;

代理模式的结构比较简单,其核心是代理类,为了让客户端能够一致性地对待真实对象和代理对象,在代理模式中引入了抽象层。

0x02 静态代理

有动态代理肯定就有静态代理,这里就先演示一下静态代理。

服务规范接口:

1
2
3
4
5
6
7
文件名:ITheServer.java

package top.sh1yan.Proxy;

public interface ITheServer {
public void getServiceList();
}

服务器本身:

1
2
3
4
5
6
7
8
9
10
11
文件名:TheServer.java

package top.sh1yan.Proxy;

public class TheServer implements ITheServer {

@Override
public void getServiceList() {
System.out.println("服务列表信息:[1,2,3]");
}
}

代理服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
文件名:TheServerProxy.java

package top.sh1yan.Proxy;

public class TheServerProxy implements ITheServer {

TheServer Ar1 = new TheServer();

@Override
public void getServiceList() {
Ar1.getServiceList();
}
}

客户机进行使用服务:

1
2
3
4
5
6
7
8
9
10
文件名:TestProxy.java

package top.sh1yan.Proxy;

public class TestProxy {
public static void main(String[] args) {
TheServerProxy Br1 = new TheServerProxy();
Br1.getServiceList();
}
}

输出结果:

1
服务列表信息:[1,2,3]

实现proxy类对服务器本身类的封装对于粒度的控制有着重要的意义。但是静态代理这个模式本身有个大问题,如果类方法数量越来越多的时候,代理类的代码量是十分庞大的。所以需要引入动态代理来解决此类问题。

0x03 动态代理

创建动态代理类会使用到 jav.lang.reflect.Proxy类和jav.lang.reflect.InvocationHandler 接口。

jav.lang.reflect.Proxy 主要用于生成动态代理类 Clas、创建代理类实例,该类实现了 jav.io.Serializable接口。

接着上面的静态代理案例,我们演示下动态代理dome:

服务规范接口:

1
2
3
4
5
6
7
文件名:ITheServer.java

package top.sh1yan.Proxy;

public interface ITheServer {
public void getServiceList();
}

服务器本身:

1
2
3
4
5
6
7
8
9
10
11
文件名:TheServer.java

package top.sh1yan.Proxy;

public class TheServer implements ITheServer {

@Override
public void getServiceList() {
System.out.println("服务列表信息:[1,2,3]");
}
}

代理服务器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
文件名:ProxyHandler.java

package top.sh1yan.Proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Date;

public class ProxyHandler implements InvocationHandler {

private Object tar; // 被代理的对象,实际的方法执行者

public Object bind(Object tar){
this.tar = tar;
// 获取动态生成的代理类的对象须借助 Proxy 类的 newProxyInstance 方法
// tar.getClass().getClassLoader() // 获取对应的 ClassLoader
// tar.getClass().getInterfaces() // 获取所有接口的Class
return Proxy.newProxyInstance(tar.getClass().getClassLoader(),tar.getClass().getInterfaces(),this);
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 该抽象接口必须导入Method这个类
Object result = null;
System.out.println(String.format("[*] 运行开始时间为[%s]",new Date().getTime()));
result = method.invoke(tar,args); // 调用 tar 的 method 方法
System.out.println(String.format("[*] 运行结束时间为[%s]",new Date().getTime()));
return result; // 返回方法的执行结果
}
}

客户机进行使用服务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
文件名:TestProxy.java

package top.sh1yan.Proxy;

public class TestProxy {
public static void main(String[] args) {
/**
* 创建代理对象并使用过程:
* a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
* b.然后根据相应的字节码转换成对应的class,
* c.然后调用newInstance()创建代理实例
*/
ProxyHandler proxy = new ProxyHandler();
ITheServer Cr1 = (ITheServer) proxy.bind(new TheServer());
Cr1.getServiceList();
}
}

输出结果:

1
2
3
[*] 运行开始时间为[1591931522467]
服务列表信息:[1,2,3]
[*] 运行结束时间为[1591931522482]

InvocationHandler 和 Proxy 的主要方法介绍如下:

java.lang.reflect.InvocationHandler:

Object invoke(Object proxy, Method method, Object[] args) 定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用

java.lang.reflect.Proxy:

static InvocationHandler getInvocationHandler(Object proxy) 用于获取指定代理对象所关联的调用处理器

static Class\<?> getProxyClass(ClassLoader loader, Class<?>… interfaces) 返回指定接口的代理类

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法

static boolean isProxyClass(Class<?> cl) 返回 cl 是否为一个代理类

Proxy.newProxyInstance() 方法创建动态代理。

newProxyInstance()方法有三个参数:

1、类加载器(ClassLoader)用来加载动态代理类。

2、一个要实现的接口的数组。

3、一个 InvocationHandler 把所有方法的调用都转到代理上。

0x04 动态代理调用过程

JDK动态代理执行方法调用的过程简图如下:

0x05 一些特性特点

  1. JDK 动态代理只需要实现 InvocationHandler 接口,重写 invoke 方法便可以完成代理的实现;

  2. jdk的代理是利用反射生成代理类 Proxyxx.class 代理类字节码,并生成对象;

  3. jdk动态代理之所以只能代理接口是因为代理类本身已经extends了Proxy,而java是不允许多重继承的,但是允许实现多个接口;

  4. JDK 动态代理是基于接口设计实现的,如果没有接口,会抛异常;

0x06 参考文章

[1] https://www.cnblogs.com/afanti/p/10199226.html

[2] https://www.zhihu.com/question/20794107

[3] https://wiki.jikexueyuan.com/project/java-reflection/java-dynamic.html

[4] http://laijianfeng.org/2018/12/Java-%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E8%AF%A6%E8%A7%A3/