Dubbo Kryo & FST RCE

VSole2022-11-25 15:31:47


漏洞简介

漏洞原理

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.alibabagroupId>    <artifactId>fastjsonartifactId>    <version>1.2.46version>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 PartEach 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 ID1.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–Attachments1.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.

注意:对于(Variable Part)变长部分,当前版本的dubbo框架使用json序列化时,在每部分内容间额外增加了换行符作为分隔,请选手在Variable Part的每个part后额外增加换行符,如:

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。

kryo和fst的调用链都比较类似,使用map序列化器反序列化时触发,

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)

核心部分调用堆栈入手,根据调用堆栈可知获取了FstSerialization解析器进行反序列化,在反序列化的过程中必然涉及到还原对象以及相关字段,而在还原HashMap 的时候对其成员进行还原时候会调用HashMap#put方法将键值对进行放入,而这个放入的过程就是触发漏洞的关键点,在放入的过程中存在两个口子,一个是hashCode,另一个是 equals。

这里调用equals口子进入toString方法,由此可知这里使用的:


org.springframework.aop.target.HotSwappableTargetSource#equals -> com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object) -> xxxx.toString()

这里再次触发equals方法,参数是我们构造的恶意JSONObject对象,

com.sun.org.apache.xpath.internal.objects.XString#equals(java.lang.Object)

在上述方法 中触发了传入的恶意JSONObject对象的toString方法:

fastjson中JSONObject是可以被序列化的,当其显式或隐式被调用toString方法时,会触发绑定对象的getter方法:

这里将会触发这个JSONObject中成员的值,也就是触发了这个TemplatesImpl对象的get系列方法,而我们的Payload入口就是getOutProperties方法,至于后面就是常规的TemplatesImpl利用手法。

下图是POC中对应的链的构造:

完整漏洞调用栈

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反序列化

在DecodeableRpcInvocation类的decode()方法(该方法会在处理RPC请求时候调用)中,通过serializationType为8、获取到反序列化器Kryo,然后调用readUTF()函数来读取dubbo协议对应的字段信息如dubbo协议版本、服务名称、服务版本、方法名、方法参数类型等:

提取方法参数类型为类数组后,再循环对参数进行Kryo反序列化。

从input中读取解析到type为HashMap,因此会调用Kryo的MapSerializer序列化器来读取input中的信息:

其中会将解析到的key和value都通过调用map.put()来放入HashMap对象中,发生在com.esotericsoftware.kryo.serializers.MapSerializer#read方法调用中,这里是有两对键值对放进去了:

往下putVal()函数中会调用key即XString类的equals()函数来判断两个key值是否相等:

到这和前面FST反序列化基本上一样了。

修复

dubbo-common 2.7.3的版本中存在kryo和fst的序列化需要的类,而在dubbo-common 2.7.4.1中,这三个包并不存在,需要自行导入。

在高版本中已将com.esotericsoftware:kryo依赖去掉了,在使用Kryo序列化器进行反序列化获取KryoObjectInput对象时会报找不到KryoException类的错误,自带的Fastjson版本为1.2.70,AutoType会自动拦截掉TemplatesImpl类。

dubbodecode
本作品采用《CC 协议》,转载必须注明作者和本文链接
Dubbo Kryo & FST RCE
2022-11-25 15:31:47
影响版本Dubbo 2.7.0 to 2.7.8Dubbo 2.6.0 to 2.6.9Dubbo all 2.5.x versions 环境复现 安装zookeeper和dubbo-samples,用idea打开dubbo-samples-api,然后修改其中的pom.xml如下: 注意,dubbo-common必须 ≤2.7.3版本。在Dubbo<=2.7.3中fastjson的版本≤1.2.46 ,这也是我们这个洞的利用点,不过这里复现使用的更高版本所以需要添加依赖, com.alibabagroupId> fastjsonartifactId> 1.2.46version>dependency>. 案例漏洞分析 FTS反序列化FTS反序列化发生在RPC协议反序列化。
攻击者可能利用此漏洞获取敏感信息或执行恶意代码。漏洞概述  漏洞名称Apache Dubbo多个反序列化漏洞漏洞编号CVE-2023-29234、CVE-2023-46279公开时间2023-12-15影响对象数量级十万级奇安信评级高危CVSS 评分7.7、8.1威胁类型信息泄露、代码执行利用可能性中POC状态未公开在野利用状态未发现EXP状态未公开技术细节状态未公开危害描述:
近日,安识科技A-Team团队监测到Apache发布安全公告,修复了一个Apache Dubbo中的远程代码执行漏洞。漏洞威胁等级:严重。该漏洞是由于在Dubbo的hessian-lite中存在反序列化漏洞,未经身份验证的攻击者可利用该漏洞在目标系统上远程执行任意代码。 对此,安识科技建议广大用户及时升级到安全版本,并做好资产自查以及预防工作,以免遭受黑客攻击。
近日,奇安信CERT监测到国外安全研究人员披露Apache Dubbo多个高危漏洞详情,其中包括Apache Dubbo YAML反序列化漏洞(CVE-2021-36162):解析攻击者构造的恶意YAML规则时,使用SnakeYAML进行反序列化从而造成远程代码执行;Apache Dubbo Hessian协议反序列化漏洞(CVE-2021-36163):当Apache Dubbo服务提...
2022年1月13日,360漏洞云团队监测到Apache发布安全公告,修复了一个Apache Dubbo中的远程代码执行漏洞。漏洞编号: CVE-2021-43297,漏洞威胁等级:高危。
在2022RSA大会上,来自Techstrong Research[1]的Mitch Ashley发表了一个名为“The Rise of API Security:It’s 10 pm – Do You Know Where Your APIs Are?”的演讲。该议题主要针对目前不断增长的API安全进行了调查和探讨。
Apache Log4j2是一款Java开源日志组件,该工具重写了Log4j框架,该日志框架被大量用于业务系统开发,用来记录日志信息。多数情况之下,开发者可能会将用户输入导致的错误信息写入日志中。此漏洞的严重性、影响面,堪称史上之最。若程序使用gradle打包,可查看编译配置文件,若在dependencies部分存在相关字段,且版本号为小于,则存在该漏洞。
这篇文章主要收集一些常见的未授权访问漏洞。未授权访问漏洞可以理解为需要安全配置或权限认证的地址、授权页面存在缺陷导致其他用户可以直接访问从而引发重要权限可被操作、数据库或网站目录等敏感信息泄露。
VSole
网络安全专家