Jackson反序列化漏洞简介(一):Jackson基本的工作原理

之前有段时间在搞jackson反序列化漏洞的检测,网上看到各种各样的文章,很多抄来抄去的,真是辣鸡,虽然我不懂,但也知道他们有些人在胡扯。本系列文章系统地介绍java里的json反序列化漏洞成因、防御方式、检测方式、利用方式。因为本人接触这些不久,如有错误,还请大佬留言指正。

本文是第一篇,讲讲jackson的基础知识。

一、Jackson的基本用法

直接贴代码吧,很简单的int和String操作,序列化反序列化,非常舒服。

恩,看起来没有任何问题,这怎么会有漏洞呢?没错,这么写没有漏洞。。。如果是我这样的玩家,使用基础的用法,不用骚操作,写上面的代码,是无法被攻击的。

但json规范里有很多额外自定义的东西,这些会引入额外的解析规则。

二、DefaultTyping的配置

jackson提供一个设置,叫enableDefaultTyping,有4个值。

下文println的输出内容已经作为注释被写到了代码里。

1、JAVA_LANG_OBJECT

JAVA_LANG_OBJECT  当类里的属性声明为一个Object时,会对该属性进行序列化和反序列化,并且明确规定类名。(当然,这个Object本身也得是一个可被序列化/反序列化的类)

例如下面的代码,我们给Person里添加一个Object object。

神奇的是,输出的String和我们平时看到的json长得不一样了,额外多出来 com.leadroyal.example.Dna 的信息。

这就是 EnableDefaultTyping ,在序列化的String里,包含类原本的一些信息,将来反的时候会进行还原。

2、OBJECT_AND_NON_CONCRETE

OBJECT_AND_NON_CONCRETE 。除了上文 提到的特征,当类里有 Interface 、 AbstractClass 时,对其进行序列化和反序列化。(当然,这些类本身需要是合法的、可以被序列化/反序列化的对象)。

例如下面的代码,这次我们添加名为 Sex 的interface,发现它被正确序列化、反序列化了,就是这个选项控制的。

3、NON_CONCRETE_AND_ARRAYS

NON_CONCRETE_AND_ARRAYS ,除了上文提到的特征,还支持上文全部类型的Array类型。

例如下面的代码,我们的Object里存放Dna的数组。

4、NON_FINAL

NON_FINAL ,包括上文提到的所有特征,而且包含即将被序列化的类里的全部、非final的属性,也就是相当于整个类、除final外的的属性信息都需要被序列化和反序列化。

例如下面的代码,添加了类型为Dna的变量,非Object也非虚,但也可以被序列化出来。

默认的、无参的enableDefaultTyping是第二个等级,OBJECT_AND_NON_CONCRETE。

总结一下,如果开启了这个功能,就可以指定被反序列化的那个类!

三、序列化时的逻辑

这里是简要的逻辑,并没有太认真地去阅读代码,各位读者将就着看,不一定对

在序列化时候,要求field可以访问到,先走getter,如果没有的话就去找field,如果还没有,就跳过这个field。

在没有开启defaultTyping的情况下,所有的field都要是基本数据类型、数组、集合类型和由常见类型组成的类等常见的数据,一旦属性里包含了不常见的东西,就直接抛异常了。

例如这个是合法的,可以被序列化和反序列化的。

例如这个是非法的,因为包含了一个不常见的类型。

在开启了 dafaultTyping的情况下,同样会对所有field都做一遍序列化,但区别在于,对不常见的类型反序列化时,需要主动指明类名,所以结构会不一样。也就是说,开启和关闭 defaultTyping 二者是相互无法解开对方的字符串的,因为协议不一样。

就拿上面这个例子,开启前后序列化出来的内容如下:

四、反序列化时的逻辑

这个代码我是读了很多次的,也调试过,可以稍微看下,但也有不准确的地方。

首先对于不开启defaultTyping的,根据json里的key-value中的key,去找对应变量的setter,找不到的话就找对应变量,还找不到的话,如果没有设置 ignore unknown,就抛异常了。因为控制的都是基本类型,所以攻击者无法发动攻击,是一种保守、安全的配置。

如果开启了defaultTyping,就不一样了,这里我们先讲两种格式,第一种格式是上面的,类名后面加一堆key-value,去反序列化指定的类;第二种是类名后面跟一个参数,但必须是基本类型,官方解释是方便使用,一般推荐String,这样就可以从String反序列化这个类了。

好,下面进入debug时间,首先样例代码就是上面那段,我们断点下在readValue上,去反序列化自动生成的那段。然后在构造方法、setter上面同样下断点,这样就可以看到栈回溯了。

第一次,断在了Person的构造函数上

来三张图,大概就是通过反射,调用了构造函数,这我这个testcase下,只会调用默认的构造函数。但看代码里,似乎有多参数的构造和一个参数的构造也是可以。

点下继续,下一次断在了setAge上。

大概就是通过反射,找到了setter方法,然后去调用赋值。

之后setName和setDna这个一模一样,后者是复杂对象,也是经过了同样的、反序列化逻辑,是嵌套的。

在没有setter方法时,应该会尝试去主动赋值,例如我们把age的setter删掉,会走下图中的代码,使用反射去赋值。

由于篇幅有限,大概就是这个样子,还是自己调试时候感受比较深刻。

这时候注意一下,前面提到,jackson代码里是可以调用到仅带有一个参数的构造方法的,当初设计这个功能可能是为了使用带String的构造方法,从而生成该对象的,也可以用其他的、仅有一个参数但是是基本类型或者数组。

再这里对各个creator做了初始化,共有9个允许的构造方法。

在寻找完毕后,可以看到我们有3个允许的构造方法。

其中,无参的构造方法是这里初始化的,之后放入第一个格子里。

有参的在这里被初始化,根据参数类型去放到相应的位置。

之前提到参数个数为1,也是这里判定的,ctor就是那个creator,将来放到9个格子里中的某一个。

五、总结

没什么好总结的吧,基本操作,多构建一些poc测测就知道了。

请期待第二篇文章~~~~~~~

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code