JAVA-动态代理

JAVA-动态代理

静态代理

在分析JAVA的动态代理机制前,我觉得应该先了解一下静态代理,这样我们能更好的了解动态代理的意义,首先呢,代理简单的说,就是我们去访问某个类的中的某个方法,我们该类不直接访问,而是通过另一个类去访问,概念有点抽象,我们来看例子

首先,我们先声明一个接口

1
2
3
4
5
6
package org.example;

public interface User {

public void show();
}

我们实现这个接口

1
2
3
4
5
6
7
8
9
10
11
package org.example;

public class Users implements User{
public Users(){

}
@Override
public void show(){
System.out.println("这个Users中实现User接口的show方法");
}
}

此时,当我们不使用代理时

1
2
3
4
5
6
7
8
9
10
11
package org.example;

public class Main {
public static void main(String[] args) {
//正常调用,不使用代理
User users = new Users();
users.show();
}
}
//运行结果
这个Users中实现User接口的show方法

我们新建一个代理类,去代理我们访问show方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;

public class ProxyClass implements User{

public User user = null;
public ProxyClass(User user) {
this.user = user;
}

@Override
public void show(){
user.show();
System.out.println("通过代理访问执行show方法");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;

public class Main {
public static void main(String[] args) {
//通过代理去访问
User users = new Users();
User ProxyTest = new ProxyClass(users);
ProxyTest.show();
}
}

//运行结果:
这个Users中实现User接口的show方法
通过代理访问执行show方法

但是静态代理有一个巨大的弊端,当我们接口中的抽象方法很多的时候,那么代理类就得重写所有的抽象方法,这就大大加大了代码量

还有一点就是,其实代理类干的事情都很机械或者说重复,就是去调用传入类中的方法,这些都可以用一个机制统一实现,那就是反射

由此,我们引入动态JAVA的动态代理

JAVA动态代理

动态代理,我们需要用到下面的这个函数

1
Proxy.newProxyInstance();

我们去看看其需要的参数

1

首先,代理的接口可以是多个的,但是,一般只代理一个接口,接下来我们来看第三个参数

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package java.lang.reflect;

/**
* {@code InvocationHandler} is the interface implemented by
* the <i>invocation handler</i> of a proxy instance.
*
* <p>Each proxy instance has an associated invocation handler.
* When a method is invoked on a proxy instance, the method
* invocation is encoded and dispatched to the {@code invoke}
* method of its invocation handler.
*
* @author Peter Jones
* @see Proxy
* @since 1.3
*/
public interface InvocationHandler {

/**
* Processes a method invocation on a proxy instance and returns
* the result. This method will be invoked on an invocation handler
* when a method is invoked on a proxy instance that it is
* associated with.
*
* @param proxy the proxy instance that the method was invoked on
*
* @param method the {@code Method} instance corresponding to
* the interface method invoked on the proxy instance. The declaring
* class of the {@code Method} object will be the interface that
* the method was declared in, which may be a superinterface of the
* proxy interface that the proxy class inherits the method through.
*
* @param args an array of objects containing the values of the
* arguments passed in the method invocation on the proxy instance,
* or {@code null} if interface method takes no arguments.
* Arguments of primitive types are wrapped in instances of the
* appropriate primitive wrapper class, such as
* {@code java.lang.Integer} or {@code java.lang.Boolean}.
*
* @return the value to return from the method invocation on the
* proxy instance. If the declared return type of the interface
* method is a primitive type, then the value returned by
* this method must be an instance of the corresponding primitive
* wrapper class; otherwise, it must be a type assignable to the
* declared return type. If the value returned by this method is
* {@code null} and the interface method's return type is
* primitive, then a {@code NullPointerException} will be
* thrown by the method invocation on the proxy instance. If the
* value returned by this method is otherwise not compatible with
* the interface method's declared return type as described above,
* a {@code ClassCastException} will be thrown by the method
* invocation on the proxy instance.
*
* @throws Throwable the exception to throw from the method
* invocation on the proxy instance. The exception's type must be
* assignable either to any of the exception types declared in the
* {@code throws} clause of the interface method or to the
* unchecked exception types {@code java.lang.RuntimeException}
* or {@code java.lang.Error}. If a checked exception is
* thrown by this method that is not assignable to any of the
* exception types declared in the {@code throws} clause of
* the interface method, then an
* {@link UndeclaredThrowableException} containing the
* exception that was thrown by this method will be thrown by the
* method invocation on the proxy instance.
*
* @see UndeclaredThrowableException
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}

该类中只有一个方法,就是invoke,这个方法是干啥的呢,很简单,就是获取并执行我们要调用的方法,因为动态加载其实是反射调用,反射调用函数就得获取我们要调用的方法名,并进行调用

这只是一个接口,我们需要实现一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;

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

public class InvocationHandlertosave implements InvocationHandler {
public User user;

public InvocationHandlertosave(User user) {
this.user = user;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
method.invoke(user, args); //反射调用,捕获参数和方法名
return null;
}
}

如此进行动态代理之后,无论我们外面调用任何的方法,都会执行该类中的invoke方法,并捕获我们要执行的方法名和参数,反射进行调用,我们来看具体的构造代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example;


import java.lang.reflect.Proxy;

public class Main {
public static void main(String[] args) {
User users = new Users();
InvocationHandlertosave Inv = new InvocationHandlertosave(users);
User ProxyTest = (User) Proxy.newProxyInstance(users.getClass().getClassLoader(), users.getClass().getInterfaces(), Inv);
ProxyTest.show();
}
}

//运行结果
这个Users中实现User接口的show方法

这里别看是去调用ProxyTest.show(),实则是先调用Inv类中的invoke方法,通过该方法,java底层去捕获到我们要执行的是show方法和show方法的参数,然后反射调用的

那对于JAVA安全,动态代理机制又有何意义呢,其实动态代理是可以为我们寻找CC链提供更多的可能的,我们来讲一个例子

F.f 方法是一个危险方法

A[O]->O.f 此时A类接受任意的类作为参数,其中还调用了O.f方法,如果我们传入的O为F,那就调用F.f方法,调用危险方法,可是事实要不是如此呢

A[O]->O.abc 此时我们传入F就不可行了,但是如果O类是一个动态代理类,那么,无论O调用任何方法,都会调用Inv中的invoke方法(Inv就是上文中的Inv实例化类,离得近,我就直接拿来用了)

如果这个invoke中执行了X.f呢,如果此时X可控,我们传入F,依然可以执行F.f危险方法

这就是JAVA动态代理为JAVA反序列化提供的更多的可能

参考文献

【Java反序列化漏洞专题-基础篇(21/09/05更新类加载部分)】https://www.bilibili.com/video/BV16h411z7o9?p=3&vd_source=c210ec42fbb60565fb74b1baf7a2c3ef