少女祈祷中...

fastjson反序列化解析过程调试

简单水一下fastjson反序列化解析过程的调试

介绍

​ fastjson是阿里巴巴开源的java中解析json的一个组件。其功能就是将json字符串与java对象进行相互转化。正如其名,解析j转化速度很快

​ 但fastjson多个版本都爆出反序列化漏洞,因此当遇到json时,可以看是否用了fastjson,再测是否存在漏洞

​ 当json中含有@type字段时,fastjson会将json解析为对应类的对象

​ 本篇文章主要是fastjson在反序列化解析的调试

调式的代码

环境

  • jdk8
  • fastjson 1.2.24

导入(pom.xml)

<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>

自定义的恶意类

package org.example.enity;

import java.io.IOException;
import java.util.Map;

public class Test {
private Map mymap;
public Test(){
System.out.println("调用了构造方法");
}
static {
System.out.println("调用了静态代码块");
}
public void setCmd(String cmd) throws IOException {
System.out.println("调用了set方法");
Runtime.getRuntime().exec(cmd);
}


public String getCmd(){
System.out.println("调用了get方法");
return "get";
}

public Map getMymap() {
return mymap;
}
}

​ 这里并不是标准的JavaBean,没有cmd属性,但有setCmd方法

调试代码

String s="{\"@type\":\"org.example.enity.Test\",\"cmd\":\"calc\"}";
JSONObject student=JSON.parseObject(s);

JSON.parseObject方法

这次是以parseObject的过程。parse方法稍微有些不同

具体代码

  1. 初步解析

可以看出,第一步就是反序列化得到一个obj对象,后面会将该对象转为JSON对象

那么整个反序列化和解析逻辑都在这个parse方法中

  1. 进入parse方法:

具体解析的函数是在JSON类中的parse方法

前面的就是简单解析进行初始化,将json字符串封装到parser对象中

  1. 进入JSON类中的parse方法:

前面几层套娃(…),到这里开始解析左边界,

当解析到{时,会返回一个对象

  1. 然后继续进入parseObject方法:

首先会进行类加载

   类加载的时候,会判断是否是一些特殊的类,然后在从从默认加载的类中寻找,如果没有才会用类加载器加载,如下

那么我们给出的类就已经加载到内存了

后面的步骤就是获取反序列化器,和进行反序列化了

  1. 进入ParserConfig类中的getDeserializer方法:

首先查询是否为已经加载的反序列化器和默认类反序列化器

最后创建一个反序列化器,并将其放入derializers中,表示已经加载的反序列化器

  1. 进入createJavaBeanDeserializer方法:

在里面会创建一个JavaBeanInfo对象

获取getter和setter

其实这一个函数才是本次调试的核心方法

跟进build方法,首先可以看到使用反射获取我们给的类的方法属性和方法

  • 获取所有setXXX(标准javaBean中存在的方法)

​ 遍历方法数组,选取出符合条件的方法

下图为部分代码截图

筛选条件:

  • 方法名长度>=4
  • 不能是静态方法
  • 不能是无参函数,且必须有返回
  • 参数个数为1
  • 方法名必须以set开头
  • 不能有注解

​ 根据这些条件能够选出setXXX,不一定是javaBean中的setXXX,只要满足条件就行(比如我定义的恶意类setCmd函数)

​ 根据这点,后面的fastjson反序列漏洞寻找能利用的方法。

然后根据方法推断出属性名

​ 类似的获取getXXX

筛选条件

  • 非静态方法
  • 无参数
  • 返回值类型继承自Collection或Map或AtomicBoolean或AtomicInteger或AtomicLong

如果按照普通的类(没有像调式代码的类一样)就会进入下面方法

最后根据前面获取的东西,构造JavaBeanInfo对象(会把一些构造器啥的丢进去)

然后会根据JavaBeanInfo对象构造反序列化器

进入ASMDeserializerFactory类中的createJavaBeanDeserializer:

​ 这就是最后构造反序列化器的函数(晕*

这里开始会进行初始化操作,对象类名设置为fastjson自定义的类名

然后反射newInstance构造一个实例返回

至此原来的createJavaBeanDeserializer函数结束。

整个获取反序列器的步骤结束

  1. 利用反序列化器进行反序列化

​ 发现后面代码调试不了,这是因为前面ASMDeserializerFactory 是用利用ASM重新生成类(ASM操作字节码),因为这个反序列化器类是代码生成的,在内存中,所以无法使用idea调试后面的反序列化操作

解决不能调试

查看在最后之前的代码

​ 起初asmEnable为 true,会走下面代码(也就是前面不能调式的代码)。如果asmEnable是false,就会直接创建一个JavaBean反序列化器(就不需要使用ASM创建一个临时的类了)。这样,后面代码可以继续调试。

这里有几种方式

​ 发现getOnly为true更适合(其他的是要求 : 类是非public、要是一个接口、属性个数大于200)

查找getOnly,发现只有一处为false

是一个Feildinfo的构造方法

这里看出方法的参数个数不能为1

发现只有在前面JaavaBean.build的时候

遍历获取getter方法的时候,才满足条件

进入if后,会调用这个new FeildInfo时进入上面的设置getOnly为true

add(fieldList, new FieldInfo(propertyName, method, null, clazz, type, 0, 0, 0, annotation, null, null));

这里进入if就行了

​ 这里是要方法的返回值是这些类中的其中一个,只有前面两个好实现,collection类和map类

​ 除此之外,也不能有之对应的setxxx,如果存在,就不会在getxxx(第二次循环时候)被解析

综上

​ 类中需要一个返回类型是map类型getxxx方法(且不能有对应的setxxx),才能对后续代码进行调试

因此调试代码中有mymap属性和getMymap方法

然后继续

进入默认的反序列化器JavaBeanDeserializer中进行反序列化,其中下面代码会触发setXXX方法

后面也没什么了,出反序列化方法

如果是parseObject方法。 最后会进行json对象的序列化

然后使用JavaBeanSerializer序列化成json对象

在其中,会调用所有属性的getter方法

这里是使用反射调用的getter方法

总结

通过调试可以发现

JSON.parseObject方法是

  • 会先会获取反序列化器,同时通过反射获取所有的setXXX和getXXX
  • 将所有属性和值放入反序列化器
  • 反序列化的时候会调用该属性的setXXX设置对象属性
  • 若是parseObject方法(除去parse方法),会转为json对象时会调用getter方法

因此上面我们的代码中

​ 我们定义的类没有cmd属性,但我们传入cmd键时,反序列化时触发setcmd方法,因此造成了命令执行。

除此,也要区分parse和parseObject,前者不会触发getXXX

​ 最后,想说好久没更新了文章了。主要是最近好一段时间都没怎么深入投入学习,并有各种事情忙。(我想立flag了

初识java安全,确实我也是小白