Java反序列化漏洞

目录

1、java序列化和反序列化的用途

2、JSON概念

3、反序列化漏洞概念

[4、Apache Commons Collections漏洞](#4、Apache Commons Collections漏洞)

[(1)、Apache Commons Collections介绍](#(1)、Apache Commons Collections介绍)

(2)、Java反射机制

[(3)、Apache Commons Collections 漏洞](#(3)、Apache Commons Collections 漏洞)

(4)关于JdbcRowSetImpl链利用的分析

5漏洞挖掘思路

6漏洞修复


1、java序列化和反序列化的用途

我们在写java代码的时候,一般是通过类 + 对象来编程,但是这样的方式只能保存在内存上面,一旦服务器关闭,那么保存在内存上面的数据就会消失,如果想在磁盘上保存或者网络传输,我们需要将对象进行序列化转换成字符串二进制流,然后我们再将字符串二进制流进行反序列化就能还原成对象

类似于php序列化和反序列化

2、JSON概念

JSON:JavaScript Object Notation 【JavaScript 对象表⽰法】
JSON是⼀种轻量级的数据交互格式. 它基于 ECMAScript (欧洲计算机协会制定的js规范)的⼀个⼦集, 采⽤完全独⽴于编程语⾔的⽂本格式来存储和表⽰数据。简单来说:JSON就是⼀种数据格式, 有⾃⼰的格式和语法, 使⽤⽂本表⽰⼀个对象或数组的信息, 因此 JSON本质是字符串. 主要负责在不同的语⾔中数据传递和交换.

objectivec 复制代码
{
"squadName": "Super hero squad",
 "homeTown": "Metro City",
 "formed": 2016,
 "secretBase": "Super tower",
 "active": true
}

我们定义好了一个person类,里面有他的构造方法以及其他get和set方法

java 复制代码
package com.wuya;

import java.io.*;

public class Person implements Serializable {
    public String name;
    public int age;

    public Person(String s, int i) {
        this.name = s;
        this.age = i;
    }

    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;
    }

}

我们通过下面的Person person去new 了一个person对象,然后再通过JSONObject 去转换成json对象,此时可以打印json,下面就是将json格式转换成对象

java 复制代码
    public static void test1(){
        Person person = new Person("fast",999);
        JSONObject json = (JSONObject) JSON.toJSON(person);
        System.out.println(json);
        System.out.println(json.get("name"));
        System.out.println(json.get("age"));
    }

    public static void test2(){
        String s ="{\"name\":\"fast\",\"age\":999}";
        JSONObject json1 = JSON.parseObject(s);
        System.out.println(json1);

        Person real = JSON.parseObject(s, Person.class);
        System.out.println(real);
        System.out.println(real.name);
        System.out.println(real.age);
    }

3、反序列化漏洞概念

我们注意上面定义的Person类,它继承了Serializable接口,如果要将一个对象进行序列化,必须继承这个接口

java 复制代码
public class TestToFile {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Person obj= new Person("wuya", 666);
        String filePath = "D:/wuya.xxx";

        // 序列化
        ObjectOutputStream  outStream = new ObjectOutputStream(new FileOutputStream(filePath));
        outStream.writeObject(obj);

        // 反序列化
        ObjectInputStream  inStream = new ObjectInputStream(new FileInputStream(filePath));
        // readObject 方法
        Person readObject = (Person)inStream.readObject();
        System.out.println("反序列化后:name="+readObject.name +",age="+readObject.age);
    }

}

通过Person来new一个对象obj,通过ObjectOutputStream new出来的对象中writeObject方法,将obj对象序列化成二进制流,其中filePath是存放二进制流的文件路径,通过010editor打开可以看到,他是有特定规范格式,在java官网上面可以看到

随后,通过ObjectInputStream new出来的对象中readObject方法将二进制流反序列化成对象,readObject反序列化成功以后,这里可以开始调用对象方法,前面Person类中的getName都可以调用

注意此时我们定义了一个Unsafeclass类,继承了Serializable接口,我们去重写readObject方法,他不会调用默认readObject方法,而是执行我下面这个,Runtime.getRuntime是获得java环境,执行计数器

java 复制代码
public class UnsafeClass implements Serializable {
    public String name;

    //重写readObject()方法
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        //执行默认的readObject()方法
        in.defaultReadObject();
        //执行命令
        Runtime.getRuntime().exec("calc.exe");
    }
}

接下来我们使用上面定义的UnsafeClass

java 复制代码
public class UnsafeTest {
    public static void main(String args[]) throws Exception {
        UnsafeClass Unsafe = new UnsafeClass();
        Unsafe.name = "hacked by wuya";
        // 序列化
        FileOutputStream fos = new FileOutputStream("D:/wuya.yyy");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(Unsafe);
        os.close();

        //从 反序列化
        FileInputStream fis = new FileInputStream("D:/wuya.yyy");
        ObjectInputStream ois = new ObjectInputStream(fis);
        UnsafeClass objectFromDisk = (UnsafeClass) ois.readObject();
        System.out.println(objectFromDisk.name);
        ois.close();
    }
}

通过序列化文件流之后,在反序列化成对象,调用重写的readObject方法,并且重写的readObject方法是可以控制的,此时就会执行恶意代码,这就是反序列化漏洞,此时这个例子是比较简单,这里只是给个思路,要想达成反序列化漏洞还是比较难的,首先必须别人的代码中有一个类似上面定义好的类,利用自定义的readObject方法执行,寻找重写readObject方法的类

4、Apache Commons Collections漏洞

(1)、Apache Commons Collections介绍

在java的jdk里面集成了各种容器,List、Map、Set等,而Apache Commons Collections是一个开源了对jdk容器再次封装集合了各种新的容器

(2)、Java反射机制

java通过(编译器)javac对源码进行编译成字节码文件,生成.Class文件,然后java虚拟机就会将Class文件翻译成机器指令吗,在操作系统上执行,所以不同平台编译的class文件都是一样的,交给jvm解析,对象的构造是通过new关键字创建出来,如果我们去跑一个程序,只有确定的情况下,jvm才会去执行,但是灵活性就稍微弱一些在程序运行的时候动态创建一个类的实例, 调用实例的方法和访问它的属性

比如下面我们静态执行下面代码

java 复制代码
  Runtime.getRuntime().exec("calc");

通过反射

java 复制代码
public static void test2(){
        try {
            //初始化Runtime类
            Class clazz = Class.forName("java.lang.Runtime");
            // 调用Runtime类中的getRuntime方法得到Runtime类的对象
            Object rt = clazz.getMethod("getRuntime").invoke(clazz);
            //再次使用invoke调用Runtime类中的方法时,传递我们获得的对象,这样就可以调用
            clazz.getMethod("exec",String.class).invoke(rt,"calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

(3)、Apache Commons Collections 漏洞

l InvokeTransformer 利用Java反射机制来创建类实例
ChainedTransformer 实现了Transformer链式调用,我们只需要传入一个Transformer数组
ChainedTransformer就可以实现依次的去调用每一个Transformer的
transform()方法
l ConstantTransformer transform()返回构造函数的对象
l TransformedMap

首先我们来到InvokerTransformer.java里面的transform方法

java 复制代码
 public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

(4)Fastjson反序列化漏洞

说到这里我们看看Alibaba开源系列

其中我们需要了解Fastjson使用方法速度快 使用广泛 测试完备 使用简单 功能完备的特点

我们试着运行下面代码,序列化的时候,会调用成员变量的get方法,私 有成员变量不会被序列化。 反序列化的时候,会调用成员变量的set方法,publibc修饰的成员全部自动赋值。

java 复制代码
package com.wuya.test;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;

public class JsonTest {
    public static void main(String[] args) {
        // 从1.2.25开始,autotype默认关闭
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // 序列化字符
        String serializedStr = "{\"@type\":\"com.wuya.test.User\",\"name\":\"wuya\",\"age\":66, \"flag\": true,\"sex\":\"boy\",\"address\":\"china\"}";//
        System.out.println("serializedStr=" + serializedStr);

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

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

        // 通过parseObject,指定为object.class
        System.out.println("JSON.parseObject(serializedStr, Object.class):");
        Object obj3 = JSON.parseObject(serializedStr, Object.class);
        System.out.println("parseObject反序列化对象名称:" + obj3.getClass().getName());
        System.out.println("parseObject反序列化:" + obj3);
        System.out.println("-----------------------------------------------\n");

        // 通过parseObject,指定为User.class
        System.out.println("JSON.parseObject(serializedStr, User.class):");
        Object obj4 = JSON.parseObject(serializedStr, User.class);
        System.out.println("parseObject反序列化对象名称:" + obj4.getClass().getName());
        System.out.println("parseObject反序列化:" + obj4);
        System.out.println("-----------------------------------------------\n");
    }

}

通过对User进行序列化得到{name= 'wuya' , age=66, flag=true, sex= 'boy' , address= 'null'},另外Fastjson反序列化的时候需要指定类名,如上面User.class,子类中包含接口或抽象类的时候,类型丢失,或者下面加上@type,给类的路径{"@type":"com.wuya.test.User" , "age":33, "flag":false, "name":"wuya"}既然你这里可以给定一个类名关键字,那我是不是可以任意构造

我们找到了如下可以利用的类,jdbc是我们java连接数据库的
com.sun.rowset.JdbcRowSetImpl

(4)关于JdbcRowSetImpl链利用的分析

从上面我们学习了绕过黑白名单的学习,接下来看 JdbcRowSetImpl 利用链的原理。
根据 FastJson 反序列化漏洞原理, FastJson 将 JSON 字符串反序列化到指定的 Java 类时,
会调用目标类的 getter 、 setter 等方法。 JdbcRowSetImpl 类的 setAutoCommit() 会调用
connect() 方法, connect() 函数如下:

具体情况是这样的我们去反序列化JdbcRowSetImpl这个类的时候,他就会去调用dataSourceName的set方法,并传入下面恶意地址代码,这个工程就会去下载恶意代码并执行从而攻击就发生了

这里反序列化调用dataSourceName的set方法

同时反序列化也会调用setAutoCommit方法,其中有一个核心方法connect

connect中有一个lookup方法,这个方法会去查找资源,其中去getDataSourceName,而dataSourceName已经被我们赋值成一个恶意链接资源,他就会下载恶意链接的代码,随后就会执行,进行反弹链接

5漏洞挖掘思路

漏洞挖掘
1、找到发送JSON序列化数据的接口
2、判断是否使用fastjon
1)非法格式报错
{"x":"
2)使用dnslog探测
{"x":{"@type":"java.net.Inet4Address" , "val":"xxx.dnslog.cn"}}
Burp 插件
https://github.com/zilong3033/fastjsonScan

6漏洞修复

1、升级JDK
6u211 / 7u201 / 8u191 /11.0.1
2、升级Fastjson到最新版
fastjson.parser.safeMode=true
3、使用安全产品过滤非法内容
4、更换其它序列化工具
Jackson/Gson

相关推荐
进击的荆棘1 小时前
C++起始之路——用哈希表封装myunordered_set和myunordered_map
开发语言·c++·stl·哈希算法·散列表·unordered_map·unordered_set
心.c1 小时前
大厂高频手写题
开发语言·前端·javascript
guslegend2 小时前
AI生图第2节:python对接gpt-image-2模型API生图
开发语言·python·gpt
原来是猿2 小时前
Linux线程同步与互斥(四):日志系统与策略模式
linux·运维·开发语言·策略模式
Rsun045513 小时前
为什么要配置maven
java·maven
卷心菜狗3 小时前
Python进阶--迭代器
开发语言·python
人道领域3 小时前
【Redis实战篇】初步基于Redis实现的分布式锁---基于黑马点评
java·数据库·redis·分布式·缓存
jr-create(•̀⌄•́)4 小时前
LeakyRelu链式法则
开发语言·python·深度学习
呱牛do it8 小时前
企业级门户网站设计与实现:基于SpringBoot + Vue3的全栈解决方案(Day 3)
java·vue