java动态代理


1. 静态代理

有一个师傅的文章讲的很好,我们经常说代理。我们可以把我们比作一个租房的人,我们找代理中介去找拥有房源的房东租房子。这个中介就可以作为一个代理。

1. 房源

房源不会做任何操作,它是被别人拿到的,我们可以用java中的接口来表示,这个类没有别的操作。

1
2
3
4
5
6
7
package src.JdkProxy.StaticProxy;    

// 租房的接口
public interface Rent {

public void rent();
}
2. 房东

房东有租出房的操作,所以作为一类

1
2
3
4
5
6
7
8
9
package src.JdkProxy.StaticProxy;    

public class Host implements Rent {
// 将房子出租
public void rent(){
System.out.println("房东要出租房子");

}
}
3. 中介类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package src.JdkProxy.StaticProxy;  

// 中介
public class Proxy {

private Host host;

public Proxy(){}

public Proxy(Host host){
// 中介要代理一个房东
this.host = host;
}

public void rent(){
host.rent();
}
}
4. 客户
1
2
3
4
5
6
7
8
9
10
11
12
13
package src.JdkProxy.StaticProxy;  

// 启动器
public class Client {
public static void main(String[] args) {
// 一个房东
Host host = new Host();
// 一个中介
Proxy proxy = new Proxy(host);
// 中介代理房东的房子出租
proxy.rent();
}
}

当然中介代理是一类人,他自然有和房东不同的操作

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
32
33
package src.JdkProxy.StaticProxy;  

// 中介
public class Proxy {

private Host host;

public Proxy(){}
public Proxy(Host host){
this.host = host;
}

public void rent(){
host.rent();
contract();
fare();
}

// 看房
public void seeHouse(){
System.out.println("中介带你看房");
}

// 收中介费
public void fare(){
System.out.println("收中介费");
}

// 签租赁合同
public void contract(){
System.out.println("签租赁合同");
}
}

总体静态代理比较简单,简单易懂,但是就是如果说房东很多时候,代码就要很繁琐。

2. 动态代理

2.1 源码
  • 动态代理的角色和静态代理的一样。需要一个实体类,一个代理类,一个启动器。
  • 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的。
  • ①:我们代理的是接口,而不是单个用户。
  • ②:代理类是动态生成的,而非静态定死。

两个重要的类

2.2 InvocationHandler
1
2
3
4

public interface InvocationHandler
// InvocationHandler是由代理实例的调用处理程序实现的接口

通过这个接口,我们可以在代理时候做一些我们被代理的实体没有实现的方法和操作。也就是说他是个处理程序模块,我们的InvocationHandler 接口就是自动执行这个方法,因此这也是java反序列中一个很好的利用点,他跟readobject一样,都是在proxy对象建立之后代理对象执行方法时候,他会自动触发。

1
2
3
4
5
Object invoke(Object proxy, 方法 method, Object[] args)

proxy 代理
method proxy执行的被代理对象的方法
args 执行方法的参数
2.3 Proxy
1
public class Proxy extends Object implements Serializable

动态代理类 (以下简称为代理类 )是一个实现在类创建时在运行时指定的接口列表的类,具有如下所述的行为。 代理接口是由代理类实现的接口

1
2
3

// 建立动态的一个动态代理类
public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

this.getClass().getClassLoader()

  • 这里的 this 是指 InvocationHandler 的实例(也就是你实现了 invoke() 方法的对象)。调用 getClass() 方法会返回当前对象的 Class 对象,然后通过 getClassLoader() 获取该类的类加载器。
  • 作用getClass().getClassLoader() 返回的是加载 InvocationHandler 实现类的类加载器。这个类加载器会用来加载动态代理类。
  • 这个类加载器是动态代理类的载体,负责加载代理对象的字节码。

userService.getClass().getInterfaces()

  • userService 是你想要代理的实际对象。通过 getClass() 可以获取该对象的 Class 对象,然后通过 getInterfaces() 获取这个类所实现的所有接口。
  • 作用getInterfaces() 返回的是目标对象 userService 实现的所有接口,代理对象需要实现这些接口。动态代理对象会实现目标类所实现的所有接口,从而可以通过代理对象调用这些接口的方法。

 this(即 InvocationHandler 实现类的实例)

  • this 是指当前的 InvocationHandler 实现对象,它实现了 invoke() 方法,负责拦截代理对象的方法调用并提供自己的处理逻辑。

因此当动态代理创建好之后,我们的所有操作都是在刚才的程序处理接口中进行的,invoke 就是程序处理程序

2.4 实例

此时有一个接口,同时开始实现这个接口

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
32
33
package src.JdkProxy.DynamicProxy;  


public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}

package src.JdkProxy.DynamicProxy;

public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("增加了一个用户");
}

@Override
public void delete() {
System.out.println("删除了一个用户");
}

@Override
public void update() {
System.out.println("更新了一个用户");
}

@Override
public void query() {
System.out.println("查询了一个用户");
}
}

此时我们实现动态代理让这个接口增加一些逻辑。

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
32
33
34
35
36
37
38
39
40
41
42
43
44
package src.JdkProxy.DynamicProxy;    

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

/*
使用InvocationHandler 接口进行程序处理程序编写
*/

public class UserProxyInvocationHandler implements InvocationHandler {

// 被代理的接口
private UserService userService;
// 创建一个实现的对象作为真实被代理对象。
public void setUserService(UserService userService) {
this.userService = userService;
}

// 动态生成代理类实例
public Object getProxy(){
Object obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), userService.getClass().getInterfaces(), this);
// 找到加载器 ,找出被代理接口实现的接口,同时写入程序处理
// 也可以在在客户端部分写这部分代码
return obj;
}

// 处理代理类实例,并返回结果
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 先执行 增加逻辑
log(method);
// 再执行被代理对象的逻辑 采用了java的反射执行 对应方法
Object obj = method.invoke(userService, args);
return obj;
}

//业务自定义需求, 这就是增加的逻辑
public void log(Method method){
System.out.println("[Info] " + method.getName() + "方法被调用");
}
}


客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package src.JdkProxy.DynamicProxy;  

import src.JdkProxy.DynamicProxy.UserServiceImpl;

public class Client {
public static void main(String[] args) {
// 真实角色
UserServiceImpl userServiceImpl = new UserServiceImpl();
// 代理角色,不存在
UserProxyInvocationHandler userProxyInvocationHandler = new UserProxyInvocationHandler();
// 如何体现 代理的是接口,无论是什么对象都统一强制转换成被代理的最开始接口
userProxyInvocationHandler.setUserService((UserService) userServiceImpl); // 设置要代理的对象

// 动态生成代理类
UserService proxy = (UserService) userProxyInvocationHandler.getProxy();

proxy.add();
proxy.delete();
proxy.update();
proxy.query();
}
}

因此其实 invoke 这个函数作用是阻止动态代理执行被代理类的方法,从而先执行增加的逻辑,然后再使用method方法的invoke 执行被代理类的方法。

3. 反序列化

其实readobjec 也是再反序列化时候自动执行,其中也有包括反射在内的利用而动态代理的invoke

入口类 A 存在 O.abc 这个方法,也就是 A[O] -> O.abc;而 O 呢,如果是一个动态代理类,在O进行一些方法调用时候首先会执行invoke 方法的增加逻辑,一旦O 的 invoke 方法里存在能调用有危险操作 .f 的方法的地方,那我们就可以进行利用

1
2
3
4
5
6
7
8
9
# readobjec 时候如果有能够传参入一个对象,URLDNS链子就是传递了URL 类,因此在readobject时候直接一直打到URL类的危险操作。


# 如果说我不传递类对象,我传递一个动态代理类。那么我进去肯定要执行方法,那我即使我不像URLDNS链子一样有危险操作,但是我可以自动触发invoke方法,invoke方法又跟readobjet方法一样可以自动触发一些操作,如果说我们能够传递一个有危险操作的类,那我就又可以执行了.
A[O] -> O.abc

O[O2] invoke -> O2.f // 此时将 B 去替换 O2
最后 ---->
O[B] invoke -> B.f // 达到漏洞利用效果

文章作者: K1T0
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 K1T0 !
  目录