反序列化漏洞攻击原理(Dubbo反序列化漏洞剖析)

867 阅读6分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

目录

一、前言

二、序列化与反序列化简介

三、反序列化怎样才会被利用?

1、说明

2、关键类说明-Transformer

3、关键类说明-TransformedMap

4、关键类说明-AnnotationInvocationHandler

5、攻击原理

6、攻击代码简介

四、Dubbo反序列化漏洞剖析

1、Dubbo的漏洞简介

2、漏洞复现步骤

3、如何解决漏洞

五、更多的反序列化漏洞

六、惯例

一、前言

最近大家都在讨论Dubbo反序列化漏洞问题。想必各个大V也都推送了相关文章。看了下各大文章差不多都是一个套路,两个步骤:第一步开始描述下Dubbo的反序列化漏洞(几乎都是官方文档内容),第二步说下怎么解决(升级版本即可)。那么我想很多同学并不知道Dubbo反序列化漏洞到底是怎么回事;也不知道,黑客如何利用漏洞进行攻击么。

如果你对上诉问题感兴趣,那么本文将问你一步一步解决反序列化漏洞到底是怎么回事,以及黑客通常会怎样利用反序列化漏洞进行攻击。

如果你是大牛,那么打扰了,哈哈哈哈。

二、序列化与反序列化简介

在进入正文之前我们简单介绍下序列化。在java里面,如果一个类需要序列化,咱们一般都需要实现一个接口Serializable,接着就是序列化与反序列化。

序列化与反序列化一般有两种方式,一种是使用开源组件如hessian等。另外就是自己实现序列化,即将对象实例在输入输出流之间转换。

自己实现比较简单,就是通过输入输出流的readObject和writeObject方法即可。

如果对象比较复杂,在序列化或者反序列化的时候需要做一些处理,则可以让需要序列化与反序列化的类实现readObject和writeObject方法即可。

private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException;

三、反序列化怎样才会被利用?

1、说明

反序列化漏洞就是指黑客序列化一个包含恶意代码的实例对象(通常是Runtime.exec来执行后台命令),此时会得到对象的字节数据。然后字节数据通过接口发送到服务端(被攻击的服务器)。服务器在反序列化出对象的过程中(readObject方法里面)就会触发触发恶意代码执行,从而达到攻击的目的。

那么是不是所有对象的反序列化都能够被利用呢?当然不是。比如我们上面说的Person对象。它在反序列化的时候(readObject)中什么也没做。黑客针对Person的反序列化做不到任何入侵。

那么怎样的对象在反序列化的时候才会被利用呢?一般都是重写了readObject方法的对象,其方法的某些逻辑会被利用。下面我们将从一个在JDK7下使用commons-collections-3.1时存在的反序列化漏洞来分析反序列化到底是怎么被利用的。

2、关键类说明-Transformer

在commons-collections中有个接口叫做Transformer,其作用就是将一个对象转换为另外一个对象。多个Transformer可以构成一个Transformer执行链,叫做:ChainedTransformer

在Transformer中有一个牛逼的实现类:InvokerTransformer,就是能够通过反射的方式执行input对象的某个方法,并将结果返回。如下由InvokeTransformer构成的执行链就达到了一个目的,就是在执行transformedChain.transform的时候能够触发Runtime.getRuntime().exec("open .")。即在Mac上打开文件管理器。

3、关键类说明-TransformedMap

TransformedMap是一个由Transformer构造的一个map。可以把任意的map都通过一个Transformer装饰成TransformedMap。装饰之后的TransformedMap的作用是:在执行map.entry.setValue的时候能够调用Transformer对入参value转换之后,然后将转换之后的结果设置到目标(被装饰)map中。

4、关键类说明-AnnotationInvocationHandler

这是JDK自带的一个类,是一个对注解调用的InvocationHandler(proxy的执行类)。这里我们并不关注其作用,而是关心其内部有个memberValues成员是一个map结构(如下)。在反序列化对象AnnotationInvocationHandler的时候,其会执行memberValues中所有的entry.setValue方法。

5、攻击原理

相信看完上面的几个类大家已经晕了,也没有看出来这些类和攻击有什么关系。接下来我们把他们的功能串起来。

A、首先ChainedTransformer链(一个Transformer)可以构造出一个可以调用Runtime.getRuntime.exec方法的链(这个执行链就是执行恶意代码的地方)。但是想要执行它,需要调用其transform方法。

B、接着TransformedMap可以使用一个Transformer(当然可以是一个ChainedTransformer)对普通map进行装饰。装饰后的TransformedMap只要执行其中的entry.setValue就能够触发其中的Transformer.transform方法执行。

C、AnnotationInvocationHandler类有个map成员,在反序列化该类的时候会触发其map的entry.setValue方法被调用。

汇总一下,大家是不是有些明白了,这是一环扣一环(从C到A倒过来看)。我们只要构建一个如下图一样的实例关系就可以实现攻击了。

第一步:通过ChainedTransformer构建一个包括恶意指令的Transformer链;第二步:将ChainedTransformer和普通Map构建成一个装饰类:TransformedMap

第三步:将TransformedMap设置为AnnotationInvocationHandler的MemberValues成员变量。

经过上面三步得到的AnnotationInvocationHandler类被序列化之后得到的二进制数据就具有攻击功能了。接下来我们看下其实在反序列化的时候是如果实现攻击的。

服务端在拿到这些二进制数据之后如果直接反序列化,就会触发如下执行流程:

  1. 首先触发AnnotationInvocationHandler反序列化执行器readObject,会触发其成员memberValues的entry.setValue执行,即执行TransformedMap中的entry.setValue
  2. 然后TransformedMap中的entry.setValue会触发其中的Transformer执行,即会触发ChainedTransformer这个执行链执行
  3. 最终就导致了恶意指令被执行,本例中会打开mac下的文件管理器。

给大家留一个思考题:为什么我们要这样大费周章执行恶意代码呢?为什么我们不自己在本地定义一个实例,让其在readObject的时候直接执行恶意代码。然后将这个实例序列化之后发送到服务端反序列化即可?

6、攻击代码简介

在构造AnnotationInvocationHandler对象的时候通过反射实现,是因为其构造方法不是public的。

四、Dubbo反序列化漏洞剖析

1、Dubbo的漏洞简介

反过来再看Dubbo的反序列化漏洞就很简单了。其实就是其在Http协议的场景时,对发送过来的字节码直接反序列化(未做任何检测)。如果我们通过上述方法就很容易实现攻击了。

下图是Dubbo在Http协议下直接反序列化客户端传递过来的对象的代码实现。

调用链大概是:

Http协议发送数据

  -> HttpProtocol.handle 

  -> HttpInvokerServiceExporter.handleRequest 

  -> HttpInvokerServiceExporter.readRemoteInvocation

  -> RemoteInvocationSerializingExporter.doReadRemoteInvocation

2、漏洞复现步骤

网上有大牛已经做过了,我就不卖弄了,大家直接参考即可:(不让贴连接,自己搜索吧)

3、如何解决漏洞

其实如果要自己解决,可以对输入流进行校验。

而Dubbo最新版本不是直接反序列化了,而是通过google提供的jsonrpc4j组件来反序列化数据。

五、更多的反序列化漏洞

大家可以参考开源的ysoserial,上面列举了很多反序列化漏洞的攻击实例,有兴趣可以看看。上面复现Dubbo反序列化漏洞的大牛也是用的这个开源工具实现。

六、惯例

如果你对本文有任何疑问或者高见,欢迎添加公众号共同交流探讨(添加公众号可以获得”Java高级架构“上10G的视频和图文资料哦)。