Fastjson 反序列化 Jndi 注入利用 JdbcRowSetImpl 链

Posted OceanSec

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Fastjson 反序列化 Jndi 注入利用 JdbcRowSetImpl 链相关的知识,希望对你有一定的参考价值。

文章目录

Fastjson 反序列化

Fastjson 组件是阿里巴巴开发的反序列化与序列化组件

Fastjson组件在反序列化不可信数据时会导致远程代码执行。究其原因:

  1. Fastjson 提供了反序列化功能,允许用户在输入 JSON 串时通过 “@type” 键对应的 value 指定任意反序列化类名
  2. Fastjson 自定义的反序列化机制会使用反射生成上述指定类的实例化对象,并自动调用该对象的 setter 方法及部分 getter 方法。攻击者可以构造恶意请求,使目标应用的代码执行流程进入这部分特定 setter 或 getter 方法,若上述方法中有可被恶意利用的逻辑(也就是通常所指的 “Gadget” ),则会造成一些严重的安全问题。官方采用了黑名单方式对反序列化类名校验,但随着时间的推移及自动化漏洞挖掘能力的提升。新 Gadget 会不断涌现,黑名单这种治标不治本的方式只会导致不断被绕过,从而对使用该组件的用户带来不断升级版本的困扰

Fastjson 组件使用案例

环境搭建

测试均是基于 1.2.23 版本的 fastjson jar 包,靶机搭建需要存在漏洞的 jar 包,但是在 github 上通常会下架存在漏洞的 jar 包,可以从 maven仓库 中找到所有版本 jar 包,方便漏洞复现

新建 maven 项目,在 pom.xml 中添加以下代码

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.23</version>
        </dependency>
    </dependencies>

点击右上角按钮

案例一:

标准 POJO 类定义如下,有 userName 和 age 两个属性,并执行

POJO 是 Plain OrdinaryJava Object 的缩写,但是它通指没有使用 Entity Beans 的普通 java 对象,可以把 POJO 作为支持业务逻辑的协助类

import com.alibaba.fastjson.JSON;

public class User 
    private int age;
    private String userName;
    public User() 
        System.out.println("User construct");
    
    public String getUserName() 
        System.out.println("getUserName");
        return userName;
    
    public void setUserName(String userName) 
        System.out.println("setUserName:" + userName);
        this.userName = userName;
    
    public int getAge() 
        System.out.println("getAge");
        return age;
    
    public void setAge(int age) 
        System.out.println("setAge:" + age);
        this.age = age;
    

    public static void main(String[] args) 
        String jsonstr = "\\"age\\":24,\\"userName\\":\\"李四\\"";
        String jsonstr2 = "\\"@type\\":\\"User\\",\\"age\\":24,\\"userName\\":\\"李四\\"";
        try 
            JSON.parse(jsonstr);
            System.out.println("=======================");
            JSON.parse(jsonstr2);
            System.out.println("=======================");
            JSON.parseObject(jsonstr);
            System.out.println("=======================");
            JSON.parseObject(jsonstr2);
            System.out.println("=======================");
            JSON.parseObject(jsonstr, User.class);
            System.out.println("=======================");
        catch (Exception e) 
            System.out.println(e.getMessage());
        
    

输出结果

输出结果说明 fastjson 在反序列化时

  1. 使用 parse 方法没有指定类的话不会触发任何方法,如果使用 @type 指定任意反序列化类名则可以触发构造方法和 set 方法
  2. 使用 parseObject 方法没有指定类的话不会触发任何方法,如果使用 @type 指定任意反序列化类名则可以触发构造方法和 get/set 方法
  3. 满足条件的 setter
    • 函数名长度大于 4 且以 set 开头
    • 非静态函数
    • 返回类型为 void 或当前类
    • 参数个数为 1 个
  4. 满足条件的 getter
    • 函数名长度大于等于 4
    • 非静态方法
    • 以 get 开头且第四个字母为大写
    • 无参数
    • 返回值类型继承自 Collection 或 Map 或 AtomicBoolean 或 AtomicInteger 或 AtomicLon

案例二:

public class User 
         public int age;
         public String userName;
         public User() 
                  System.out.println("User construct");
         

执行反序列化

String jsonstr = "\\"age\\":24,\\"userName\\":\\"李四\\"";
try 
         User user = JSON.parseObject(jsonstr, User.class);
         System.out.println("age:" + user.age);
         System.out.println("userName:" + user.userName);
catch (Exception e) 
         System.out.println(e.getMessage());

输出结果

对于没有 setter 的可见 public Filed,fastjson 会正确赋值

案例三:

将 Field userName 改为私有,不提供 setter

public class User 
         public int age;
         private String userName;
         public User() 
                  System.out.println("User construct");
         
         public String getUserName() 
                  return userName;
         

执行反序列化

String jsonstr = "\\"age\\":24,\\"userName\\":\\"李四\\"";
try 
         User user = JSON.parseObject(jsonstr, User.class);
         System.out.println("age:" + user.age);
         System.out.println("userName:" + user.getUserName());
catch (Exception e) 
         System.out.println(e.getMessage());

以上说明对于不可见 Field 且未提供 setter 方法,fastjson 默认不会赋值

将反序列化代码修改为如下:

String jsonstr = "\\"age\\":24,\\"userName\\":\\"李四\\"";
try 
         User user = JSON.parseObject(jsonstr, User.class, Feature.SupportNonPublicField);
         System.out.println("age:" + user.age);
         System.out.println("userName:" + user.getUserName());
catch (Exception e) 
         System.out.println(e.getMessage());

对于未提供 setter 的私有 Field,fastjson 在反序列化时需要显式提供参数 Feature.SupportNonPublicField 才会正确赋值

案例四

反序列化解析的多种方式

先构建需要序列化的User类:User.java

package com.fastjson;

public class User 
    private String name;
    private int age;

    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;
    

再使用fastjson组件

package com.fastjson;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;

public class Main 

    public static void main(String[] args) 
        //创建一个用于实验的user类
        User user1 = new User();
        user1.setName("lala");
        user1.setAge(11);

        //序列化
        String serializedStr = JSON.toJSONString(user1);
        System.out.println("serializedStr="+serializedStr);

        //通过parse方法进行反序列化,返回的是一个JSONObject
        Object obj1 = JSON.parse(serializedStr);
        System.out.println("parse反序列化对象名称:"+obj1.getClass().getName());
        System.out.println("parse反序列化:"+obj1);

        //通过parseObject,不指定类,返回的是一个JSONObject
        Object obj2 = JSON.parseObject(serializedStr);
        System.out.println("parseObject反序列化对象名称:"+obj2.getClass().getName());
        System.out.println("parseObject反序列化:"+obj2);

        //通过parseObject,指定类后返回的是一个相应的类对象
        Object obj3 = JSON.parseObject(serializedStr,User.class);
        System.out.println("parseObject反序列化对象名称:"+obj3.getClass().getName());
        System.out.println("parseObject反序列化:"+obj3);
    

以上使用了三种形式反序列化
结果如下:

//序列化
serializedStr="age":11,"name":"lala"
//parse(..)反序列化
parse反序列化对象名称:com.alibaba.fastjson.JSONObject
parse反序列化:"name":"lala","age":11
//parseObject(..)反序列化
parseObject反序列化对象名称:com.alibaba.fastjson.JSONObject
parseObject反序列化:"name":"lala","age":11
//parseObject(,class)反序列化
parseObject反序列化对象名称:com.fastjson.User
parseObject反序列化:com.fastjson.User@3d71d552

parseObject(…)其实就是parse(…)的一个封装,对于parse的结果进行一次结果判定然后转化为JSONOBject类型。

public static JSONObject parseObject(String text) 
        Object obj = parse(text);
        return obj instanceof JSONObject ? (JSONObject)obj : (JSONObject)toJSON(obj);
    

而 parseObject(,class) 好像会调用 class 加载器进行类型转化,但这个细节不是关键,就不研究了

那么三种反序列化方式除了返回结果之外,还有啥区别?

在执行过程调用函数上有不同。

package com.fastjson;
import com.alibaba.fastjson.JSON;
import java.io.IOException;

public class FastJsonTest 

    public String name;
    public String age;
    public FastJsonTest() throws IOException 
    

    public void setName(String test) 
        System.out.println("name setter called");
        this.name = test;
    

    public String getName() 
        System.out.println("name getter called");
        return this.name;
    

    public String getAge()
        System.out.println("age getter called");
        return this.age;
    

    public static void main(String[] args) 
        Object obj = JSON.parse("\\"被屏蔽的type\\":\\"com.fastjson.FastJsonTest\\",\\"name\\":\\"thisisname\\", \\"age\\":\\"thisisage\\"");
        System.out.println(obj);

        Object obj2 = JSON.parseObject("\\"被屏蔽的type\\":\\"com.fastjson.FastJsonTest\\",\\"name\\":\\"thisisname\\", \\"age\\":\\"thisisage\\"");
        System.out.println(obj2);

        Object obj3 = JSON.parseObject("\\"被屏蔽的type\\":\\"com.fastjson.FastJsonTest\\",\\"name\\":\\"thisisname\\", \\"age\\":\\"thisisage\\"",FastJsonTest.class);
        System.out.println(obj3);
    

结果如下:

//JSON.parse("")
name setter called
com.fastjson.FastJsonTest@5a2e4553
//JSON.parseObject("")
name setter called
age getter called
name getter called
"name":"thisisname","age":"thisisage"
//JSON.parseObject("",class)
name setter called
com.fastjson.FastJsonTest@e2144e4

结论:

  • parse("") 会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法
  • parseObject("") 会调用反序列化目标类的特定 setter 和 getter 方法(此处有的博客说是所有setter,个人测试返回String的setter是不行的,此处打个问号)
  • parseObject("",class) 会识别并调用目标类的特定 setter 方法及某些特定条件的 getter 方法

特定的setter和getter的调用都是在解析过程中的调用。(具体是哪些setter和getter会被调用,我们将在之后讲到)

之所以parseObject("")有区别就是因为parseObject("")比起其他方式多了一步toJSON操作,在这一步中会对所有getter进行调用。

漏洞原理

fastjson 支持使用 @type 指定反序列化的目标类,并且会自动调用类中属性的特定的 set,get 方法

package com.fastjson;
import java.util.Properties;

public class Person 
    //属性
    public String name;
    private String full_name;
    private int age;
    private Boolean sex;
    private Properties prop;
    //构造函数
    public Person()
        System.out.println("Person构造函数");
    
    //set
    public void setAge(int age)
        System.out.println("setAge()");
        this.age = age;
    
    //get 返回Boolean
    public Boolean getSex()
        System.out.println("getSex()");
        return this.sex;
    
    //get 返回ProPerties
    public Properties getProp()
        System.out.println("getProp()");
        return this.prop;
    
    //在输出时会自动调用的对象ToString函数
    public String toString() 
        String s = "[Person Object] name=" + this.name + " full_name=" + this.full_name  + ", age=" + this.age + ", prop=" + this.prop + ", sex=" + this.sex;
        return s;
    

@type 反序列化实验

import com.alibaba.fastjson.JSON;

public class type 

    public static void main(String[] args) 
        String eneity3 = "\\"@type\\":\\"com.fastjson.Person\\", \\"name\\":\\"lala\\", \\"full_name\\":\\"lalalolo\\", \\"age\\": 13, \\"prop\\": \\"123\\":123, \\"sex\\": 1";
        //反序列化
        Object obj = JSON.parseObject(eneity3, com.fastjson.Person.class);
        //输出会调用obj对象的tooString函数
        System.out.println(obj);
    

结果如下

public name 反序列化成功
private full_name 反序列化失败
private age setAge 函数被调用
private sex getsex 函数没有被调用
private prop getprop 函数被成功调用

可以得知:

  • public 修饰符的属性会进行反序列化赋值,private 修饰符的属性不会直接进行反序列化赋值,而是会调用 setxxx ( xxx 为属性名)的函数进行赋值
  • getxxx (xxx为属性名)的函数会根据函数返回值的不同,而选择被调用或不被调用

Jndi注入利用JdbcRowSetImpl链

原理

反序列化 Gadget 主流都是使用 JNDI,现阶段都是在利用根据 JNDI 特征自动化挖掘 Gadget。JNDI 采取什么样的方式注入以及能否注入成功和 JDK 的版本有关,因为 JDK 为了阻止反序列化攻击也实施了相应的缓解措施

简单来说,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 服务端除了直接绑定远程对象之外,还可以通过 References 类来绑定一个外部的远程对象(当前名称目录系统之外的对象)。绑定了 Reference 之后,服务端会先通过 Referenceable.getReference() 获取绑定对象的引用,并且在目录中保存。当客户端在 lookup() 查找这个远程对象时,客户端会获取相应的 object factory,最终通过 factory 类将 reference 转换为具体的对象实例

fastjson<1.2.24 JdbcRowSetImpl链

com.sun.rowset.JdbcRowSetImpl 为例说明。根据 FastJson 反序列化漏洞原理,FastJson 将 JSON 字符串反序列化到指定的 Java 类时,会调用目标类的 getter、setter 等方法。JdbcRowSetImpl 类的 setAutoCommit() 会调用 connect() 函数,connect() 函数如下:

 private Connection connect() throws SQLException 
        if(this.conn != null) 
            return this.conn;
         else if(this.getDataSourceName() != null) 
            try 
                InitialContext var1 = new InitialContext();
                DataSource var2 = (DataSource)var1.lookup(this.getDataSourceName());
                return this.getUsername() != null && !this.getUsername(14-java安全——fastjson1.2.24反序列化JdbcRowSetImpl利用链分析

14-java安全——fastjson1.2.24反序列化JdbcRowSetImpl利用链分析

14-java安全——fastjson1.2.24反序列化JdbcRowSetImpl利用链分析

14-java安全——fastjson1.2.24反序列化JdbcRowSetImpl利用链分析

深入理解JNDI注入与Java反序列化漏洞利用

JNDI-Injection-Exploit