今年4月份爆出JBoss EAP/AS <=6.版本的4446*端口存在反序列化rRCE漏洞,本文对该漏洞进行复现并做了以下分析。
0x01 漏洞分析
测试版本:jboss-6.1.0.Final
运行jboss-6.1.0,发现开启了很多端口,包括3873,4446等。在bindings-jboss-beans.xml
文件中描述了4446端口为实现JBoss Remoting Connector的套接字。
既然是JBoss Remoting Connector。定位到jboss-remoting.jar
中的org.jboss.remoting.transport.socket.ServerThread
类。该线程类处理接收到的socket数据流。分析关键方法durun()
。
通过this.socketWrapper
获取socket的输入流和输出流,然后进入processInvocation()
处理。在获取输入流的过程中,如果随便传输一字符串会报错,定位到:
org.jboss.remoting.transport.socket.ClientSocketWrapper#createInputStream
。
通过org.jboss.remoting.marshal.PreferredStreamUnMarshaller#getMarshallingStream(java.io.InputStream)
处理socket接收的数据流is
。中间经java.io.ObjectInputStream#readStreamHeader()
进行处理。判断是否满足s0 == STREAM_MAGIC && s1 == STREAM_VERSION
。如下图所示:
其中,STREAM_MAGIC
和STREAM_VERSION
恰好是熟悉的反序列化头aced0005
。
因此向该socket发送序列化对象继续进行测试。但进行到org.jboss.remoting.transport.socket.ServerThread#processInvocation
又抛出异常。具体跟踪问题到:this.readVersion(inputStream);
。
如上图所示,通过this.readVersion(inputStream);
读取到的version
为-1,因此抛出异常。
通过inputStream.read();
读取version
,因此可控制socket输入流定义version。那么如何设置输入流使读取到的version不为-1呢?
跟踪inputStream.read()
,经过java.io.ObjectInputStream.BlockDataInputStream#read() -> java.io.ObjectInputStream.BlockDataInputStream#refill()
。
初始unread
值为 0,进入java.io.ObjectInputStream.BlockDataInputStream#readBlockHeader
,这里需说明,在readBlockHeader
方法中,需构造in.peek();
取到的值为TC_BLOCKDATA
,即0x77
,
final static byte TC_BLOCKDATA = (byte)0x77;
然后返回hbuf[1]
,即0x77
后面的值,这里设置为0x01
。即通过readBlockHeader()
方法后返回值为1。
然后将读取到的值n
赋值给unread
,此时unread
为1,再进入in.read(buf, 0, Math.min(unread, MAX_BLOCK_SIZE));
进行处理,即继续读取数量为unread
的数据流自位置0开始放入buf
中。
重新回到java.io.ObjectInputStream.BlockDataInputStream#read()
中:
返回(buf[pos++] & 0xFF)
,此时buf
经以上处理后为0x01
后1位的数据,即0x02
,pos
为0,即返回buf[0]&0xFF
——2。
综上,设置0xaced0005770102数据流进入以上流程处理后,返回的version为2。
然后进入org.jboss.remoting.transport.socket.ServerThread#completeInvocation()
方法。
通过org.jboss.remoting.transport.socket.ServerThread#versionedRead->org.jboss.invocation.unified.marshall.InvocationUnMarshaller#read(java.io.InputStream, java.util.Map, int) -> org.jboss.remoting.serialization.impl.java.JavaSerializationManager#receiveObject
进入receiveObject()
方法。
读取上面读取到的version
值,如果为2,则进入receiveObjectVersion1_2
处理。继续跟进,看到熟悉的objInputStream.readObject();
。
但是,还没有结束!
这里简单的序列化”test”字符串,拼接到上述分析的0xaced0005770102
数据后,直接反序列化会报错:
继续分析报错原因:定位到java.io.ObjectInputStream#readObject0
方法:
通过tc = bin.peekByte()
得到的数据进行分支判断,直接序列化后的数据头为-84
,无法匹配以下任意值。
而正常反序列化流程需进入readOrdinaryObject(unshared)
:
而TC_OBJECT
值为0x73
,即去掉序列化头aced0005
后剩余的部分。
至此,整个反序列化流程分析完成。
0x02 漏洞复现(CB链)
Common/lib下存在commons-beanutils.ja
r包,因此利用cb链进行反序列化利用。如下图所示,经序列化去掉序列化头-84,-19,0,5(aced0005)
后一位即为TC_OBJECT
标志位——115(0x73)
,因此只要去掉反序列化标志头[-84,-19,0,5]
,从115(0x73)开始拼接到上述分析得到的acde0005770102
后,即可实现反序列化利用。
整个流程调用链如下:
成功利用CB链反序列化从而执行任意命令。
0x03 其他利用(JNDI利用)
除了利用CB链,JBoss中自己的org.jboss.ejb3.mdb.ProducerManagerImpl
类——jboss-ejb3-core.jar
,可反序列化实现JNDI利用。关键代码如下:
同时,经测试,JBoss开放的3873端口,即Ejb3 Remoting Connector
,也可触发该反序列化漏洞。
0x04 总结
该漏洞虽然是反序列化漏洞,但中间复现过程可谓一波三折。在构造输入数据流时需要对代码进行认真调试和分析,才能更好理解数据是如何被处理以及最终成功实现反序列化。