学习
实践
活动
工具
TVP
写文章
专栏首页Timeline Sec【漏洞分析】Dubbo Pre-auth RCE(CVE-2021-30179)

【漏洞分析】Dubbo Pre-auth RCE(CVE-2021-30179)

作者:lz2y@Timeline Sec

本文字数:3220

阅读时长:8~10min

声明:仅供学习参考使用,请勿用作违法用途,否则后果自负

0x01 漏洞复现

《CVE-2021-30179:Apache Dubbo RCE复现》

0x02 调试准备

暂无

0x03 漏洞分析

Dubbo使用DecodeHandler#received方法来接收来自socket的连接,当接收到请求时会先调用DecodeHandler#decode方法处理请求,其将调用DecodeableRpcInvocation.java#decode方法处理数据

在138行通过方法名和desc获取服务端中的方法描述,如果为空则判断是否为泛类引用

判断方法名是否为invoke或者invokeAsync,desc是否为Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;,如果不满足则直接抛出异常

decode完成之后将调用HeaderExchangeHandler.java#received方法处理请求,若为泛型引用,则将调用GenericFilter#invoke方法

判断是否满足泛型引用条件,通过getArguments()方法获取参数,并取第一个参数为方法名,第二个参数为方法名的类型,第三个参数为args。通过反射寻找服务端提供的方法,如果没有找到则抛出异常。

接下来将通过获取请求中的generic参数来选择通过raw.return/nativejava/bean反序列化参数成pojo对象,这个CVE漏洞的入口就在这里了

我们依次分析一下触发点

1、设置generic为raw.return或者true,将调用PojoUtils#realize方法

PojoUtils.realize(args, params, method.getGenericParameterTypes());

// PojoUtils#realize
    public static Object[] realize(Object[] objs, Class<?>[] types, Type[] gtypes)    {
        ...
        Object[] dests = new Object[objs.length];
        for (int i = 0; i < objs.length; i++) {
            dests[i] = realize(objs[i], types[i], gtypes[i]);
        }

调用PojoUtils#realize

public static Object realize(Object pojo, Class<?> type, Type genericType) {
        return realize0(pojo, type, genericType, new IdentityHashMap<Object, Object>());

调用PojoUtils#realize0,若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方法

最后利用反射执行method.invoke(dest, value);,就可以使用org.apache.xbean.propertyeditor.JndiConverter的setAsText发起JNDI注入了

private static Object realize0(Object pojo, Class<?> type, Type genericType, final Map<Object, Object> history) {
      ...
        if (pojo instanceof Map<?, ?> && type != null) {
            Object className = ((Map<Object, Object>) pojo).get("class");
            if (className instanceof String) {
                try {
                    type = ClassUtils.forName((String) className);
                } catch (ClassNotFoundException e) {
                    // ignore
                }
            }
            ...
            Map<Object, Object> map;
            if (!type.isInterface() && !type.isAssignableFrom(pojo.getClass())) {
                try {
                    ...
                } catch (Exception e) {
                    map = (Map<Object, Object>) pojo;
                }
            }
        if (Map.class.isAssignableFrom(type) || type == Object.class) {...}
        else if (type.isInterface()) {...}
        else {
            Object dest = newInstance(type);
                history.put(pojo, dest);
                for (Map.Entry<Object, Object> entry : map.entrySet()) {
                    Object key = entry.getKey();
                    if (key instanceof String) {
                        String name = (String) key;
                        Object value = entry.getValue();
                        if (value != null) {
                            // 获取对应的set方法
                            Method method = getSetterMethod(dest.getClass(), name, value.getClass());
                            Field field = getField(dest.getClass(), name);
                            if (method != null) {
                                if (!method.isAccessible()) {
                                    method.setAccessible(true);
                                }
                                Type ptype = method.getGenericParameterTypes()[0];
                                value = realize0(value, method.getParameterTypes()[0], ptype, history);
                                try {
                                    // 反射执行方法
                                    method.invoke(dest, value);
                                } catch (Exception e) {...}
                                else if (field != null) {...}
                                
        }

调用栈如下:

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)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

2、设置generic为bean,将遍历args,如果args[i]为JavaBeanDescriptor的实例,则调用JavaBeanSerializeUtil#deserialize进行处理

JavaBeanSerializeUtil.deserialize((JavaBeanDescriptor) args[i]);

// JavaBeanSerializeUtil#deserialize
    public static Object deserialize(JavaBeanDescriptor beanDescriptor) {
        return deserialize(
                beanDescriptor,
                Thread.currentThread().getContextClassLoader());
    }

调用

JavaBeanSerializeUtil#instantiateForDeserialize以获得对象,name2Class负责把 Class.forName 的返回值转换为 Class,而instantiate则实例化对象,最终返回一个JavaBeanDescriptor描述的对象

private static Object instantiateForDeserialize(JavaBeanDescriptor beanDescriptor, ClassLoader loader,IdentityHashMap<JavaBeanDescriptor, Object> cache) {
        ...
        Object result;
        if (beanDescriptor.isArrayType()) {...}
        else {
            try {
                Class<?> cl = name2Class(loader, beanDescriptor.getClassName());
                result = instantiate(cl);
                cache.put(beanDescriptor, result);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
        return result;
    }

接着调用JavaBeanSerializeUtil#deserializeInternal进行反序列化,如果beanDescriptor.isBeanType()(只需要实例化JavaBeanDescriptor时指定即可),则将遍历beanDescriptor,获取property及value,调用getSetterMethod获取对应的set方法

最后利用反射执行method.invoke(dest, value);,就可以使用org.apache.xbean.propertyeditor.JndiConverter的setAsText发起JNDI注入了

private static void deserializeInternal(Object result, JavaBeanDescriptor beanDescriptor, ClassLoader loader,IdentityHashMap<JavaBeanDescriptor, Object> cache) {
        if (beanDescriptor.isEnumType() || beanDescriptor.isClassType() || beanDescriptor.isPrimitiveType()) {
            return;
        }
        if(beanDescriptor.isArrayType()){...}
        ...
        else if (beanDescriptor.isBeanType()) {
            for (Map.Entry<Object, Object> entry : beanDescriptor) {
                String property = entry.getKey().toString();
                Object value = entry.getValue();
                if (value == null) {
                    continue;
                }
                if (value instanceof JavaBeanDescriptor) {...}
                // 获取对应的set方法
                Method method = getSetterMethod(result.getClass(), property, value.getClass());
                boolean setByMethod = false;
                try {
                    if (method != null) {
                        // 反射执行方法
                        method.invoke(result, value);
                        setByMethod = true;
                    }
                }catch (Exception e) {
                    ...
                }
        }

调用栈如下:

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)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

3、设置generic为nativejava,将遍历args,如果args[i]的类型为byte,以args[]为参实例化一个UnsafeByteArrayInputStream,再通过反射获得NativeJavaSerialization,再调用NativeJavaSerialization#readObject方法

if (byte[].class == args[i].getClass()) {
    try (UnsafeByteArrayInputStream is = new UnsafeByteArrayInputStream((byte[]) args[i])) {
        args[i] = ExtensionLoader.getExtensionLoader(Serialization.class).getExtension(GENERIC_SERIALIZATION_NATIVE_JAVA).deserialize(null, is).readObject();
          } catch (Exception e) {
     throw new RpcException("Deserialize argument [" + (i + 1) + "] failed.", e);
    }
}else{...}

NativeJavaSerialization#readObject调用inputStream的readObject()方法,相当于调用了UnsafeByteArrayInputStream的readObject()方法

由于UnsafeByteArrayInputStream没有readObject()故调用ObjectInputStream#readObject()方法进行反序列化,我们可以通过以此为入口触发CC4的gadget

 public Object readObject() throws IOException, ClassNotFoundException {
        return inputStream.readObject();
    }

调用栈如下:

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)
received:51, DecodeHandler (org.apache.dubbo.remoting.transport)
run:57, ChannelEventRunnable (org.apache.dubbo.remoting.transport.dispatcher)
runWorker:1142, ThreadPoolExecutor (java.util.concurrent)
run:617, ThreadPoolExecutor$Worker (java.util.concurrent)
run:745, Thread (java.lang)

0x04 个人总结

个人认为CVE-2021-30179的主要思路就是Apache Dubbo在处理泛类引用时,提供了多种通过反序列化方式得到对象再生成pojo对象的选择。

在进行反序列化过程中没有做好防护,轻易相信用户提供的数据,直接将其进行反序列化操作,导致一些恶意对象的实例化以及相对应Gadget的触发,从而造成RCE。

在2.7.10中,使用了黑名单来阻断raw.return和bean这两条链

org\apache\dubbo\common\utils\PojoUtils.java#realize0

org\apache\dubbo\common\beanutil\JavaBeanSerializeUtil.java#name2Class

org\apache\dubbo\common\utils\SerializeClassChecker.java#validateClass

而nativejava则通过判断配置文件是否允许nativejava的反序列化

参考链接:

https://www.anquanke.com/post/id/197658

文章分享自微信公众号:
Timeline Sec

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:lz2y
原始发表时间:2021-07-31
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • CVE-2021-30179:Apache Dubbo RCE复现

    Apache Dubbo是一个分布式框架,致力于提供高性能透明化的RPC远程服务调用方案,以及SOA服务治理方案。Apache Dubbo在实际应用场景中主要负...

    Timeline Sec
  • Nuxeo RCE漏洞分析

    链接地址:https://v.qq.com/x/page/k3242hhatge.html

    小生观察室
  • Atlassian Crowd RCE漏洞分析

    最近,当我正在进行侦察时,我遇到了一个Atlassian Crowd应用程序。如果您不熟悉Crowd,它是一个集中的身份管理应用程序,允许公司“从多个目...

    洛米唯熊
  • Jenkins RCE漏洞分析汇总

    由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,雷神众测以及文章作者不为此承担任何责任。 雷神众测拥有对此文章的修改和...

    安恒网络空间安全讲武堂
  • Apache Solr最新RCE漏洞分析

    Apache Solr爆出RCE 0day漏洞(漏洞编号未给出),这里简单的复现了对象,对整个RCE的流程做了一下分析,供各位看官参考。

    FB客服
  • 禅道全版本rce漏洞分析

    禅道项目管理软件是一款国产的,基于LGPL协议,开源免费的项目管理软件,它集产品管理、项目管理、测试管理于一体,同时还包含了事务管理、组织管理等诸多功能,是中小...

    C4rpeDime
  • CVE-2019-16759 vBulletin 5.x 0day pre-auth RCE 漏洞复现

    vBulletin 是世界上用户非常广泛的PHP论坛,很多大型论坛都选择vBulletin作为自己的社区。vBulletin高效,稳定,安全,在中国也有很多大型...

    墙角睡大觉
  • Kibana RCE漏洞详细分析

    Elasticsearch Kibana是荷兰Elasticsearch公司的一套开源的、基于浏览器的分析和搜索Elasticsearch仪表板工具,作为Ela...

    FB客服
  • Joomla 3.4.6-RCE漏洞复现

    Joomla是一套全球有名的CMS系统,基于PHP语言加上MySQL数据库所开发出来的WEB软件系统,目前最新版本是3.9.12。可以在多种不同的平台上部署并且...

    墙角睡大觉
  • SpringCloud Function SPEL表达式 RCE 漏洞分析

    title: SpringCloud Function SPEL RCE analysis date: 2022-03-29 12:46:03 tags:

    用户2202688
  • 关于近期Microsoft Exchange多个高危漏洞——ProxyLogon

    近期网上曝出Microsoft Exchange存在多个高危漏洞,通过组合利用这些漏洞能够在未经身份验证的情况下远程获取目标服务器权限。其中包括CVE-20...

    tinyfisher
  • Adobe ColdFusion RCE(CVE-2019-7839) 漏洞分析

    Adobe ColdFusion 是一个商用的快速开发平台。它可以作为一个开发平台使用,也可以提供Flash远程服务或者作为 Adobe Flex应用的后台服务...

    Seebug漏洞平台
  • Windows DNS API RCE漏洞分析及PoC构造

    根据 Microsoft 2017 年 10 月安全通告,多个版本 Windows 中的 dnsapi.dll 在处理 DNS response 时可导致 SY...

    FB客服
  • CVE-2019-11043: PHP 7 RCE漏洞分析

    研究人员在PHP 7中找出有个远程代码执行(RCE)漏洞,该漏洞CVE编号为CVE-2019-11043。攻击者利用该漏洞只需要访问通过精心伪造的URL就可以在...

    猿哥
  • Adobe ColdFusion RCE(CVE-2019-7839) 漏洞分析

    原文链接:https://paper.seebug.org/999/ 英文版本: https://paper.seebug.org/1000/

    知道创宇云安全
  • 漏洞分析 | Dubbo2.7.7反序列化漏洞绕过分析

    北京时间2020-6-22日Apache官方发布了Dubbo 2.7.7版本,其中修复了一个严重的远程代码执行漏洞(CVE-2020-1948),这个漏洞是由...

    云鼎实验室
  • CVE-2017-12629 - Apache Solr XXE & RCE 漏洞分析

    风流
  • DLink RCE漏洞CVE-2019-17621分析

    上一篇文章分了一下ARM系统的路由器漏洞,本次打算尝试一下MIPS系统,于是选了最近DLink路由器的漏洞CVE-2019-17621作为目标。同样一路走来各种...

    FB客服
  • Confluence 未授权 RCE (CVE-2019-3396) 漏洞分析

    看到官方发布了预警,于是开始了漏洞应急。漏洞描述中指出Confluence Server与Confluence Data Center中的Widget Conn...

    Seebug漏洞平台

扫码关注腾讯云开发者

领取腾讯云代金券