前言
在分析readObject 和writeObject中 都会进去NonProxy方法中去分析,那么可以猜到,反序列化中,当满足一定的条件,会调用代理模式,这篇就来讲一下Java的动态代理机制。
代理模式
用一句话来形容下代理模式:设置一个中间代理来访问目标对象,以达到增强原对象的功能和简化访问方式。
代理模式的作用
-
隔离:避免目标类被直接调用,保护目标类不被曝光
-
扩展功能:可以在代理类基础上添加自己的业务逻辑,而不用去破坏原有的核心功能
Java 代理模式
Java的代理模式分为三种:静态代理
、动态代理
和cglib代理
,学习反序列化漏洞中,Jdk动态代理是我们需要关注的重点。
静态代理
类似于接口或类的继承,功能提供者和功能代理者实现一样的接口。
比如,出个反序列化漏洞的poc ,大家都想一睹为快,这时候,我委托作者让我来公布,但是我公布的时候就可以加一些新的要求,在里面,比如让大家关注我的公众号,
首先,有一个功能接口,代表查看poc的能力
public interface IFunction {
// 功能接口:代表这个接口的功能
public void readPoc();
}
然后,需要一个功能提供者来实现这个readPoc
接口的类
public class FunctionProvider implements IFunction {
@Override
public void readPoc() {
System.out.println("某某某POC");
}
}
之后,还需要一个实现接口的代理类,他实现了IFunction
接口和readPoc()
方法的调用,就可以查看poc了
public class Proxy implements IFunction {
FunctionProvider provider;
Proxy(FunctionProvider provider) {
this.provider = provider;
}
@Override
public void readPoc() {
provider.readPoc();
}
}
这个时候就实现了静态代理,这时候 我这个中间人不答应了,我想给我的公众号打个广告,我给它扩展个功能
public class Proxy implements IFunction {
FunctionProvider provider;
Proxy(FunctionProvider provider) {
this.provider = provider;
}
@Override
public void readPoc() {
ad(true);
provider.readPoc();
ad(false);
}
public void ad(boolean status){
if(status)
System.out.println("查看前 ,大家请关注公众号《跟着石头学安全》");
else
System.out.println("看完了,大家不要忘记关注公众号《跟着石头学安全》");
}
}
之后写个方法调用一下,Proxy 有一个readPoc()方法,不过调用方法时,会将我自己添加的一些扩展打印让用户看到。写个测试代码调用一下。
public class ProxyTest {
public static void main(String[] args) {
FunctionProvider provider = new FunctionProvider();
Proxy proxy = new Proxy(provider);
proxy.readPoc();
}
}
可以看到,静态代理模式下,通过扩展功能提供者,进行一些功能的增强,这些都没有影响原核心类的内容和结构。
通过对静态代理的定义,每次新增一个函数就需要实现一个代理接口,显然是非常耗时
的。接口多了维护难度大
,优点就是可以在不修改目标对象的前提下,扩展目标对象的功能
动态代理
一般到这一步,大家都能猜到动态代理的优点,就是改良静态代理不足的(静态代理这个模式会随着类方法数量的增加,代理类的代码量会变得庞大,代码冗余)
先来看一下动态代理的思想:为传入进来的任意对象动态生成一个代理对象,这个代理对象默认实现了原始对象的所有接口
看到这里,大家可能会联系到Java 反射
,没错,动态代理就是Java反射的应用场景之一
Java 反射
反射机制是java语言提供的一种基础功能,赋予程序在运行时自省(introspect)的能力,通过反射可以在运行时访问Java对象的属性,方法,构造方法等(关于Java反射,我会另有一篇文章专门提及)
同样 还是举个栗子来说明一下
还是以之前的例子,我一看,关注的人还挺多的,然后我拥有很多很多0day(至于为什么有这么多我也不知道),这时候我一个人管理不过来,我就雇了很多人来管理,大家只要关注我的公众号,然后留言就会有人来私聊你们,这个人可能是员工甲,也有可能是员工乙。
来看看在动态代理里面所扮演的角色
俺的公众号相当于动态代理,包含真实对象,以便对抽象对象进行操作,相当于中介
Interface Target 抽象对象,声明真实对象和代理对象的公共接口
Real Target 真实对象,代理对象多代表的对象,最后被引用的对象
抽象对象
这次可以阅读更多的poc了
public interface IFunctions {
// 也就是提到的抽象对象,可以理解为功能,比如:可以从《跟着石头学安全》获取0day
void readPocs();
}
真实对象, 金poc
public class IPoc1 implements IFunctions{
// 这是提到的真实对象,创建一个类IPoc1。可以理解为公众号公布的poc之一,
@Override
public void readPocs() {
System.out.println("这是《跟着石头学安全》公众号公布的0day1");
}
}
接下来就是动态代理类
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynPorxy implements InvocationHandler {
// 这就是我的公众号《跟着石头学安全》,可爱们 通过我的公众号来获取某一poc
private Object objFactory;
public Object getObjFactory(){
return objFactory;
}
public void setObjFactory(Object objFactory){
this.objFactory = objFactory;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("在获取0day前 ,请大家关注我的公众号哦,这里是增强功能处");
System.out.println("获取金0day 0day具体是"+this.getClass().getSimpleName());
Object invoke = method.invoke(objFactory,args); // 通过反射调用方法本身
System.out.println("在获取0day后 ,请大家关注我的公众号哦,这里是增强功能处");
return null;
}
}
这里出现了新的类InvocationHandler
等到后面,我会解释
测试类
import java.lang.reflect.Proxy;
public class TestProxy {
public static void main(String[] args) {
IPoc1 poc1 = new IPoc1();
DynPorxy dynpoc = new DynPorxy();
dynpoc.setObjFactory(poc1);
IFunctions ifunctionInstance = (IFunctions) Proxy.newProxyInstance(IPoc1.class.getClassLoader(),IPoc1.class.getInterfaces(),dynpoc);
ifunctionInstance.readPocs();
}
}
这里提到了jdk 代理类获取代理对象
先来看结果,
虽然没有和静态代理专门写一个接口实现代理类,但是却达到了相同的功能,
动态代理的语法
在java的动态代理机制中,有两个重要的类或接口:一个是 InvocationHandler(Interface);另一个则是 Proxy(Class),这一个类和接口是实现我们动态代理所必须用到的。
在上面的代码中,涉及了一个非常重要的类Proxy,通过Proxy的静态方法newProxyInstance 动态创建代理
public static Object newProxyInstance(ClassLoader loader,//指定当前目标对象使用类加载器
Class<?>[] interfaces,//目标对象实现的接口的类型
InvocationHandler h)//事件处理器
InvocationHandler
InvocationHandler 也是第一次遇到,他是一个接口,用来处理代理实例上的方法调用并返回结果。当方法在与之关联的代理实例上调用时,将在调用处理程序上调用此方法。
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
通过Proxy
动态产生的代理会调用InvocationHandler实现类
那么 Porxy 是怎么动态创建不同的接口类型的,以及动态代理的逻辑,来看一下源代码
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
/*
* 查找或生成指定的代理类
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* 使用指定的调用处理程序调用其构造函数。
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
我们通过Proxy的newProxyInstance
,新建了一个实例,然后代码通过反射获取Proxy类 保存到cl
private static Class<?> getProxyClass0(ClassLoader loader,
Class<?>... interfaces) {
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
// If the proxy class defined by the given loader implementing
// the given interfaces exists, this will simply return the cached copy;
// otherwise, it will create the proxy class via the ProxyClassFactory
return proxyClassCache.get(loader, interfaces);
}
这里直接获取接口,获取不到通过ProxyClassFactory
生成
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// prefix for all proxy class names
private static final String proxyClassNamePrefix = "$Proxy";
// next number to use for generation of unique proxy class names
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
String proxyPkg = null; // package to define proxy class in
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
/*
* Record the package of a non-public proxy interface so that the
* proxy class will be defined in the same package. Verify that
* all non-public proxy interfaces are in the same package.
*/
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
/*
* Choose a name for the proxy class to generate.
*/
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
/*
* Generate the specified proxy class.
*/
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} catch (ClassFormatError e) {
/*
* A ClassFormatError here means that (barring bugs in the
* proxy class generation code) there was some other
* invalid aspect of the arguments supplied to the proxy
* class creation (such as virtual machine limitations
* exceeded).
*/
throw new IllegalArgumentException(e.toString());
}
}
}
Proxy class 的类生成原理
// 生成proxyPkg
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf('.');
String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
"non-public interfaces from different packages");
}
}
}
if (proxyPkg == null) {
// if no non-public proxy interfaces, use com.sun.proxy package
proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
}
// 字段:proxyClassNamePrefix
private static final String proxyClassNamePrefix = "$Proxy";
// 字段 num
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
之后,生成指定的代理类
//创建class字节码内容
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
// 生成class<?> 对象
return defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);
返回的就是 $Proxy0.class
跟了下代码 ,大概了解了动态代理的实现,委托类动态生成了一个$Proxy0.class
,也就是上面的分析代码,该代理类会实现委托类的接口,并把接口调用转发到InvocationHandler#invoke
最终调用真实委托类的方法
动态代理总结
动态代理无需知晓真实对象的差异,可以统一由代理方法创建对应的真实对象实例,并引用其中的方法
Cglib 动态代理
这个不是重点,简单了解一下
cglib (Code Generation Library )是一个第三方代码生成类库,运行时在内存中动态生成一个子类对象从而实现对目标对象功能的扩展。基于ASM机制实现,通过生成业务类的子类作为代理类。
cglib特点
-
JDK的动态代理有一个限制,就是使用动态代理的对象必须实现一个或多个接口。如果想代理没有实现接口的类,就可以使用CGLIB实现。
-
CGLIB是一个强大的高性能的代码生成包,它可以在运行期扩展Java类与实现Java接口。它广泛的被许多AOP的框架使用,例如Spring AOP和dynaop,为他们提供方法的interception(拦截)。
-
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它需要你对JVM内部结构包括class文件的格式和指令集都很熟悉。
cglib与动态代理最大的区别就是
-
使用动态代理的对象必须实现一个或多个接口
-
使用cglib代理的对象则无需实现接口,达到代理类无侵入。
https://juejin.cn/post/6844903828131692551
https://segmentfault.com/a/1190000011291179
https://www.guildhab.top/2020/07/java-%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E5-%E8%A7%A3%E5%AF%86-ysoserial-java%E5%8A%A8%E6%80%81%E4%BB%A3%E7%90%86%E6%9C%BA%E5%88%B6/
本文始发于微信公众号(跟着石头学安全):java反序列化-动态代理
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论