Java反序列化工具:Gadgetinspector First Glimpse - 极悦
专注Java教育14年 全国咨询/投诉热线:444-1124-454
极悦LOGO图
始于2009,口口相传的Java黄埔军校
首页 hot资讯 Java反序列化工具:Gadgetinspector First Glimpse

Java反序列化工具:Gadgetinspector First Glimpse

更新时间:2021-11-04 10:08:12 来源:极悦 浏览1429次

关于Gadgetinspector First Glimpse这个工具

此工具不用于查找漏洞。相反,它使用已知的 source->…->sink 技巧或其类似的特性来发现分支利用链或新的利用链。

这个工具在整个应用程序的类路径中寻找一个链。

该工具执行一些合理的风险估计(污点判断、污点转移等)。

这个工具会产生误报而不是漏报(其实还是会漏掉的,这是由作者使用的策略决定的,可以在下面的分析中看到)。

该工具基于字节码分析。对于Java应用,很多时候我们没有源代码,只有War包、Jar包或者class文件。

此工具不会生成可直接使用的 Payload。具体的利用结构也需要人工参与。

序列化和反序列化

Java学习中,序列化是将对象的状态信息转换为可以存储或传输的形式的过程。转换后的信息可以存储在磁盘上。在网络传输过程中,可以是字节、XML、JSON等形式,将字节、XML、JSON等形式的信息还原为对象的逆过程称为反序列化。

在Java中,对象序列化和反序列化广泛应用于RMI(远程方法调用)和网络传输。

Java 中的序列化和反序列化库

JDK(对象输入流)

XStream(XML,JSON)

杰克逊(XML,JSON)

根森(JSON)

JSON-IO(JSON)

FlexSON(JSON)

Fastjson(JSON)

不同的反序列化库在反序列化不同的类时有不同的行为。不同的“魔法方法”会被自动调用,这些自动调用的方法可以作为反序列化的入口点(源码)。如果这些自动调用的方法调用了其他的子方法,那么调用链中的一个子方法也可以作为源,相当于知道了调用链的前端,从一个子方法开始寻找不同的分支。一些危险的方法(sink)可以通过方法的层调用来达到。

对象输入流

比如一个类实现了Serializable接口,那么ObjectInputStream.readobject在反序列化的时候会自动找到该类的readObject、readResolve等方法。

比如一个类实现了Externalizable接口,那么ObjectInputStream.readobject在反序列化的时候会自动找到这个类的readExternal等方法。

杰克逊

ObjectMapper.readValue反序列化一个类时,会自动查找反序列化类的无参构造函数、包含基类型参数的构造函数、属性的setter、属性的getter等。

在接下来的分析中,以JDK自带的ObjectInputStream为例。

控制数据类型 => 控制代码

在反序列化漏洞中,如果我们控制了数据类型,我们就控制了代码。这是什么意思?根据理解,写了下面的例子:

public class TestDeserialization {
    interface Animal {
        public void eat();
    }
    public static class Cat implements Animal,Serializable {
        @Override
        public void eat() {
            System.out.println("cat eat fish");
        }
    }
    public static class Dog implements Animal,Serializable {
        @Override
        public void eat() {
            try {
                Runtime.getRuntime().exec("calc");
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("dog eat bone");
        }
    }
    public static class Person implements Serializable {
        private Animal pet;
        public Person(Animal pet){
            this.pet = pet;
        }
        private void readObject(java.io.ObjectInputStream stream)
                throws IOException, ClassNotFoundException {
            pet = (Animal) stream.readObject();
            pet.eat();
        }
    }
    public static void GeneratePayload(Object instance, String file)
            throws Exception {
        //Serialize the constructed payload and write it to the file
        File f = new File(file);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
    public static void payloadTest(String file) throws Exception {
        //Read the written payload and deserialize it
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        Object obj = in.readObject();
        System.out.println(obj);
        in.close();
    }
    public static void main(String[] args) throws Exception {
        Animal animal = new Dog();
        Person person = new Person(animal);
        GeneratePayload(person,"test.ser");
        payloadTest("test.ser");
//        Animal animal = new Cat();
//        Person person = new Person(animal);
//        GeneratePayload(person,"test.ser");
//        payloadTest("test.ser");
    }
}

为方便起见,将所有类都写在一个类中进行测试。在Person类中有Animal类的属性pet,它是Cat和Dog的接口。在序列化中,我们可以控制Per的pet是Cat对象还是Dog对象,所以在反序列化中,pet.eat()inreadObject的具体方向是不同的。如果pet是Cat类对象,就不会去执行有害代码Runtime.getRuntime().exec("calc");,但是如果pet是Dog类对象,它就会去执行有害代码。

即使有时在声明时为类属性分配了特定对象,它仍然可以通过 Java 中的反射进行修改。如下:

public class TestDeserialization {
    interface Animal {
        public void eat();
    }
    public static class Cat implements Animal, Serializable {
        @Override
        public void eat() {
            System.out.println("cat eat fish");
        }                           
    }
    public static class Dog implements Animal, Serializable {
        @Override
        public void eat() {
            try {
                Runtime.getRuntime().exec("calc");
            } catch (IOException e) {
                e.printStackTrace();
            }
            System.out.println("dog eat bone");
        }
    }
    public static class Person implements Serializable {
        private Animal pet = new Cat();
        private void readObject(java.io.ObjectInputStream stream)
                throws IOException, ClassNotFoundException {
            pet = (Animal) stream.readObject();
            pet.eat();
        }
    }
    public static void GeneratePayload(Object instance, String file)
            throws Exception {
        //Serialize the constructed payload and write it to the file
        File f = new File(file);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
    public static void payloadTest(String file) throws Exception {
        //Read the written payload and deserialize it
        ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
        Object obj = in.readObject();
        System.out.println(obj);
        in.close();
    }
    public static void main(String[] args) throws Exception {
        Animal animal = new Dog();
        Person person = new Person();
        //Modify private properties by reflection
        Field field = person.getClass().getDeclaredField("pet");
        field.setAccessible(true);
        field.set(person, animal);
        GeneratePayload(person, "test.ser");
        payloadTest("test.ser");
    }
}

在Person 类中,您不能通过构造函数或setter 方法或其他方法为pet 赋值。该属性在声明时就已经定义为Cat类的对象,但是使用反射可以将pet修改为Dog类的对象,所以反序列化的时候还是会跑到有害代码中去。

这只是我个人对作者的看法:“控制数据类型,你控制代码”。在Java反序列化漏洞中,很多时候是利用Java的多态特性来控制代码的方向,最终达到作恶的目的。

魔法方法

在上面的例子中,我们可以看到Person的readobject方法在反序列化的时候并没有被手动调用。当 ObjectInputStream 反序列化对象时会自动调用它。作者将自动调用的方法调用为“魔术方法”。

使用 ObjectInputStream 反序列化时的几种常见魔术方法:

Object.readObject()

Object.readResolve()

Object.finalize()

一些可序列化的 JDK 类实现了上述方法,并且还会自动调用其他方法(可以用作已知的入口点):

哈希表

Object.hashCode()

Object.equals()

优先队列

Comparator.compare()

Comparable.CompareTo()

一些水槽:

Runtime.exec(),在目标环境中直接执行命令的最简单直接的方式

Method.invoke(),需要正确选择方法和参数,并通过反射执行Java方法

RMI/JNDI/JRMP等,通过引用远程对象间接实现任意代码执行的效果

以上就是关于“Java反序列化工具:Gadgetinspector First Glimpse”的介绍,大家如果想了解更多相关知识,不妨来关注一下极悦的Java开发工具,里面对于工具的介绍有很多,大家可以多去学习一下。

提交申请后,顾问老师会电话与您沟通安排学习

免费课程推荐 >>
技术文档推荐 >>