漏洞简介
Dubbo Provider即服务提供方默认使用dubbo协议来进行RPC通信,而dubbo协议默认是使用Hessian2序列化格式进行对象传输的,但是针对Hessian2序列化格式的对象传输可能会有黑白名单设置的限制,参考:https://github.com/apache/dubbo/pull/6378
针对这种场景,攻击者可以通过更改dubbo协议的第三个flag位字节来更改为使用Kryo或FST序列化格式来进行Dubbo Provider反序列化攻击从而绕过针对Hessian2反序列化相关的限制来达到RCE。
-
Dubbo 2.7.0 to 2.7.8
-
Dubbo 2.6.0 to 2.6.9
-
Dubbo all 2.5.x versions (not supported by official team any longer)
环境复现
安装zookeeper和dubbo-samples,用idea打开dubbo-samples-api,然后修改其中的pom.xml如下:
注意,dubbo-common必须≤2.7.3版本。
在Dubbo<=2.7.3中fastjson的版本≤1.2.46 ,这也是我们这个洞的利用点,不过这里复现使用的更高版本所以需要添加依赖,
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.46</version>
</dependency>
使用POC进行测试:
Dubbo的协议设计
由于Dubbo可以支持很多类型的反序列化协议,以满足不同系统对RPC的需求,比如:
•跨语言的序列化协议:Protostuff、ProtoBuf、Thrift、Avro、MsgPack
• 针对Java语言的序列化方式:Kryo、FST
• 基于Json文本形式的反序列化方式:Json、Gson
Dubbo中对支持的协议做了一个编号,每个序列化协议都有一个对应的编号,以便在获取TCP流量后,根据编号选择相应的反序列化方法,因此这就是Dubbo支持这么多序列化协议的秘密,但同时也是危险所在。
org.apache.dubbo.common.serialize.Constants中可见每种序列化协议的编号:
而在Dubbo的RPC通信时,对流量的规定最前方为header,而header中通过指定SerializationID,确定客户端和服务提供端通信过程使用的序列化协议。
Dubbo通信的具体数据包规定如下图所示:
虽然Dubbo的provider默认使用hessian2协议,但我们可以自由的修改SerializationID,选定危险的(反)序列化协议,例如kryo和fst。
Dubbo RPC数据包格式
•Magic(魔术) - Magic High & Magic Low (16 bits)
用值标识dubbo协议:0xdabb
•Req/Res (1 bit)
标识这是一个请求或响应。请求 : 1;响应 : 0。
•2 Way (1 bit)
Only useful when Req/Res is 1 (Request), expect for a return value from server or not. Set to 1 if need a return value from server.
•Event (1 bit)
Identifies an event message or not, for example, heartbeat event. Set to 1 if this is an event.
•Serialization ID (5 bit)
标识序列化类型:fastjson 的值为 6。
•Status (8 bits)
Only useful when Req/Res is 0 (Response), identifies the status of response
•20 - OK
–30 - CLIENT_TIMEOUT
–31 - SERVER_TIMEOUT
–40 - BAD_REQUEST
–50 - BAD_RESPONSE
–60 - SERVICE_NOT_FOUND
–70 - SERVICE_ERROR
–80 - SERVER_ERROR
–90 - CLIENT_ERROR
–100 - SERVER_THREADPOOL_EXHAUSTED_ERROR
•Request ID (64 bits)
Identifies an unique request. Numeric (long).
•Data Length (32)
序列化后内容(可变部分)的长度,以字节为单位。数字(整数)。
•Variable Part
Each part is a byte[] after serialization with specific serialization type, identifies by Serialization ID.
Every part is a byte[] after serialization with specific serialization type, identifies by Serialization ID
1.If the content is a Request (Req/Res = 1), each part consists of the content, in turn is:
•Dubbo version
–Service name
–Service version
–Method name
–Method parameter types
–Method arguments
–Attachments
1.If the content is a Response (Req/Res = 0), each part consists of the content, in turn is:
•Return value type, identifies what kind of value returns from server side: RESPONSE_NULL_VALUE - 2, RESPONSE_VALUE - 1, RESPONSE_WITH_EXCEPTION - 0.
–Return value, the real value returns from server.
Dubbo version bytes (换行符)
Service name bytes (换行符)
...
漏洞分析
FTS反序列化
FTS反序列化发生在RPC协议反序列化。
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)
在上述方法中首先通过serializationType索引序列化器,当TypeId为8时使用Kryo,TypeId为9时使用Fst。
instantiate:79, FSTMapSerializer (org.nustaq.serialization.serializers)
instantiateAndReadWithSer:497, FSTObjectInput (org.nustaq.serialization)
readObjectWithHeader:366, FSTObjectInput (org.nustaq.serialization)
readObjectFields:708, FSTObjectInput (org.nustaq.serialization)
instantiateAndReadNoSer:562, FSTObjectInput (org.nustaq.serialization)
readObjectWithHeader:370, FSTObjectInput (org.nustaq.serialization)
readObjectFields:708, FSTObjectInput (org.nustaq.serialization)
instantiateAndReadNoSer:562, FSTObjectInput (org.nustaq.serialization)
readObjectWithHeader:370, FSTObjectInput (org.nustaq.serialization)
readObjectInternal:327, FSTObjectInput (org.nustaq.serialization)
instantiate:77, FSTMapSerializer (org.nustaq.serialization.serializers)
instantiateAndReadWithSer:497, FSTObjectInput (org.nustaq.serialization)
readObjectWithHeader:366, FSTObjectInput (org.nustaq.serialization)
readObjectInternal:327, FSTObjectInput (org.nustaq.serialization)
readObject:307, FSTObjectInput (org.nustaq.serialization)
readObject:102, FstObjectInput (org.apache.dubbo.common.serialize.fst)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:73, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decodeBody:132, DubboCodec (org.apache.dubbo.rpc.protocol.dubbo)
org.springframework.aop.target.HotSwappableTargetSource#equals -> com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object) -> xxxx.toString()
com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)
完整漏洞调用栈
exec:-1, Runtime (java.lang)
:-1, Pwner8957425893700 (ysoserial)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:-1, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:-1, Constructor (java.lang.reflect)
newInstance:-1, Class (java.lang)
getTransletInstance:-1, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:-1, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:-1, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
write:-1, ASMSerializer_1_TemplatesImpl (com.alibaba.fastjson.serializer)
write:270, MapSerializer (com.alibaba.fastjson.serializer)
write:44, MapSerializer (com.alibaba.fastjson.serializer)
write:280, JSONSerializer (com.alibaba.fastjson.serializer)
toJSONString:863, JSON (com.alibaba.fastjson)
toString:857, JSON (com.alibaba.fastjson)
equals:-1, XString (com.sun.org.apache.xpath.internal.objects)
equals:104, HotSwappableTargetSource (org.springframework.aop.target)
putVal:-1, HashMap (java.util)
put:-1, HashMap (java.util)
instantiate:79, FSTMapSerializer (org.nustaq.serialization.serializers)
instantiateAndReadWithSer:497, FSTObjectInput (org.nustaq.serialization)
readObjectWithHeader:366, FSTObjectInput (org.nustaq.serialization)
readObjectInternal:327, FSTObjectInput (org.nustaq.serialization)
readObject:307, FSTObjectInput (org.nustaq.serialization)
readObject:102, FstObjectInput (org.apache.dubbo.common.serialize.fst)
decode:116, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decode:73, DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo)
decodeBody:132, DubboCodec (org.apache.dubbo.rpc.protocol.dubbo)
decode:122, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decode:82, ExchangeCodec (org.apache.dubbo.remoting.exchange.codec)
decode:48, DubboCountCodec (org.apache.dubbo.rpc.protocol.dubbo)
decode:90, NettyCodecAdapter$InternalDecoder (org.apache.dubbo.remoting.transport.netty4)
decodeRemovalReentryProtection:508, ByteToMessageDecoder (io.netty.handler.codec)
callDecode:447, ByteToMessageDecoder (io.netty.handler.codec)
channelRead:276, ByteToMessageDecoder (io.netty.handler.codec)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:357, AbstractChannelHandlerContext (io.netty.channel)
channelRead:1410, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelRead:379, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelRead:365, AbstractChannelHandlerContext (io.netty.channel)
fireChannelRead:919, DefaultChannelPipeline (io.netty.channel)
read:166, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:719, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:655, NioEventLoop (io.netty.channel.nio)
processSelectedKeys:581, NioEventLoop (io.netty.channel.nio)
run:493, NioEventLoop (io.netty.channel.nio)
run:989, SingleThreadEventExecutor$4 (io.netty.util.concurrent)
run:74, ThreadExecutorMap$2 (io.netty.util.internal)
run:30, FastThreadLocalRunnable (io.netty.util.concurrent)
run:-1, Thread (java.lang)
Kryo反序列化
修复
原文始发于微信公众号(雁行安全团队):Dubbo Kryo & FST RCE