Java反序列化漏洞之静态代理与动态代理(5)
2023-06-15 17:11:53 # Web Security # Java Deserialization

前言

看了很多前辈的文章,都提到了Java的一大核心机制: 动态代理机制(Dynamic Proxy), 这一知识点我断断续续看了有好几天,不复杂但有点绕,所以一直没有写出这篇博文,好在查询了众多资料,现在有了一些自己的理解。

这一章将探究JAVA静态和动态代理机制 - 有助于我们理解YSoSerial的playload实现机制。

Java代理机制

什么是代理机制

Proxy Design实际上是一种软件设计模式,

A proxy receives client requests, does some work (access control, caching, etc.) and then passes the request to a service object.

因为安全问题,client不允许直接access服务端上的某个服务,需要一个Proxy class先处理client的请求,通过Proxy class来访问这个服务,再返回给Client。

而Proxy Class就是代理, 委托Proxy Class帮他和客户建立连接的服务就是Delegate Class - 委托类(被代理)。

通常Proxy和Delegate委托类有相同的方法,且Proxy拥有委托类的reference,可以调用其方法。在调用其方法的时候,Proxy可以在其基础上添加或者修改功能,这样就不需要修改到委托类,修改代理类本身就可以了。

一个形象的例子:A是房东,他需要出租自己的房子,他会委托代理B(中介)把房子挂在网上,当客户C需要租房时,C会直接和代理B进行交流而不是直接和房东A交流。

所以我们可以得出使用代理模式的好处:

  • Proxies 让我们可以在不改变委托类的情况下,自定义修改委托类方法代码执行前和执行后的behaviour
  • Proxy pattern是安全的。一个远程的代理可以提供一个proxy stub给客户端, 然后在Server端call the implementation(此处是不是很像我们在Java反序列化漏洞之JAVA RMI原理、流程(2)中所提到的JAVA RMI呢?)

静态代理

静态代理就是手动编写代理类。

委托类和代理类要实现相同的接口

preview

来看看静态代理是如何实现的:

1
2
3
public interface TestDelegate {
void sayHello();
}
1
2
3
4
5
6
public class TestImp implements TestDelegate {
@Override
public void sayHello() {
System.out.println("hello");
}
}

代理类handle一个委托类的object:

1
2
3
4
5
6
7
8
public class TestProxy implements TestDelegate{
private TestDelegate testDelegate = new TestImp();
@Override
public void sayHello() {
System.out.println("this is a hello world message");//自定义在前面输出信息
testDelegate.sayHello();//call委托类的方法
}
}

Client尝试访问sayHello():

1
2
3
4
5
6
public class Test {
public static void main(String[] args) {
TestProxy testProxy = new TestProxy();
testProxy.sayHello();//client没有直接访问TestImp的权限,只能通过TestProxy访问
}
}

image-20210809150635426

静态代理的缺陷:

程序员要手动为每一个委托类编写一个对应的代理类,如果当前系统有上百千个类,工作量就太大了。而动态代理可以帮助我们解决这个问题。

动态代理

动态代理,简单的说就是用一个代理类帮我们动态生成了不同类的代理,我们不需要手动针对每个委托类编写一个队形的代理类。

InvocationHandler接口:负责提供调用代理操作。

是由代理对象调用处理器实现的接口,定义了一个invoke()方法,每个代理对象都有一个关联的接口。当代理对象上调用方法时,该方法会被自动转发到InvocationHandler.invoke()方法来进行调用。

看代码

1
2
3
public interface TestDelegate {
void sayHello();
}
1
2
3
4
5
6
public class TestImp implements  TestDelegate {
@Override
public void sayHello() {
System.out.println("hello");
}
}
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
package dynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//中间类必须要实现InvocationHandler
public class Agency implements InvocationHandler {
private Object target;
public Object bind(Object target){
this.target=target;

//通过传入进来的target(委托)类,通过直接得到他的类加载器,实现的接口和当前的invoationHandler来返回一个新的代理,相比于静态代理我们需要手动写一个代理类,动态代理根据传入的参数自动生成一个代理类
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理被执行代码的时候 invoke()会被call
//这里你可以通过判断方法名等等,做出不同的修改
System.out.println("Delegate Start");
Object res = method.invoke(target,args);//此处使用了反射,使用target里面的method
System.out.println("Delegate End...");
return res;
}
}

1
2
3
4
5
6
7
8
9
10
11
12
package dynamic;

public class DynamicTest {

public static void main(String[] args) {
Agency agency = new Agency();
TestDelegate test = (TestDelegate) agency.bind(new TestImp());//此处我们得到了一个代理类(动态生成的)
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//这里会有生成的动态代理类的字节码文件
test.sayHello();//执行该代理类的方法时,代理类中的invoke()会被call
}
}

image-20210811223911668

由此可见,动态代理的作用:

  • 动态代理只需要实现一个代理类,而静态代理需要实现多个代理类

  • 解耦,通过参数就可以判断真实类,不需要实现实例化,更加灵活多变

Reference

Java 动态代理作用是什么? - bravo1988的回答 - 知乎

Java 动态代理作用是什么? - ZeaTalk的回答 - 知乎

Java Dynamic Proxy

Proxies in Java — Static & Dynamic

JAVA安全基础(三)– java动态代理机制

Java 静态代理&动态代理学习

Java 反序列化漏洞(5) – 解密 YSoSerial : Java动态代理机制