F4de's blog F4de's blog
首页
WP整理
技术文章
学习笔记
其它随笔
关于|友链

F4de

Syclover | Web
首页
WP整理
技术文章
学习笔记
其它随笔
关于|友链
  • php安全

  • python安全

  • Java安全

    • Java反射特性摘要
    • Java反序列化-URLDNS
    • Java反序列化-CC1
    • Java反序列化-CC5
    • Java反序列化-CC6
    • Fastjson(1)-初探以及利用方式
    • Fastjson(2)-TemplatesImpl利用链
    • 谈一谈Java动态加载字节码
      • ClassLoader
        • 双亲委派模型
        • 到源码中看看
        • 几个重要方法
        • 自定义加载器
      • 动态加载
        • 使用Classloader直接加载字节码
        • 使用URLClassLoader远程加载字节码
        • 使用Unsafe直接加载字节码
        • 使用Proxy来直接加载字节码
        • 使用BCEL来加载字节码
      • 参考
  • 其他

  • 技术文章
  • Java安全
F4de
2020-12-18

谈一谈Java动态加载字节码的方式

# ClassLoader

要说Java动态加载字节码,就不得不谈Java的ClassLoader。当然,它也不是我简简单单几段文字能够说得清的,这里对于ClassLoader的介绍姑且算是抛砖引玉,背后的更多东西有兴趣的读者可以自行挖掘。

Java程序运行前需要先编译成为.class文件,在类初始化的时候会调用java.lang.ClassLoader来加载.class文件,ClassLoader会调用一些方法把这些字节码文件变成一个java.lang.Class对象。

graph LR

Java源文件--javac编译-->Java字节码文件--ClassLoader-->JVM
1
2
3

Java类必须经过JVM的加载之后才能运行,而ClassLoader的作用就是加载Java类。Java中有三种加载器:

  • Bootstarp ClassLoader(引导类加载器)
  • Extension ClassLoader(扩展类加载器)
  • App ClassLoader (系统类加载器)

其中App ClassLoader是默认的加载器。也就是说,如果我们在加载类的时候不指定加载器,则会默认使用App ClassLoader。

下图为JVM的架构图:

img

# 双亲委派模型

ClassLoader是基于双亲委派模型来搜索类的,每一个ClassLoader对象都有一个父类加载器的引用(不是继承关系,而是包含关系),这个父类加载器可以用getParten()来获取。

对于App ClassLoader来说,它的父类加载器是Extension ClassLoader;对于Extension ClassLoader来说,它的父类加载器是Bootstarp ClassLoader;而Bootstarp ClassLoader没有父类加载器。此外,开发人员会编写一些自定义的类加载器,一般来说,这些类加载器的父类加载器是App ClassLoader。

当一个ClassLoader加载某个类的时候,会把这个类交给其父类加载器来加载,而整个过程是由上而下的检查的,也就是说首先由最顶层的Bootstarp ClassLoader加载,如果没加载到,则再由Extension ClassLoader来加载,如果还是没加载到,再交给App ClassLoader来加载,如果还是没找到,则会返回到一开始发起这个委托请求的ClassLoader,由这个加载器到指定的文件系统、网络等URL来获取要加载的类。如果还是没有获取到,则会抛出ClassNotFoundException异常。

下面是一个简单图示:

image-20201212221339594

其实在loadClass在调用findClass之前还会调用一次findLoadedClass方法来检测JVM是否已经加载过该类了,如果已经加载则会直接返回该类的对象。上图中默认JVM没有加载该类。

在网上找的另外一张:

0_13301699801ybH

使用双亲委派模型可以避免类的重复加载,同样增加了安全性。

当父亲已经加载了该类的时候,就没有必要 ClassLoader再加载一次。考虑到安全因素,我们试想一下,如果不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。

public class Main {
    public static void main(String[] args) {
        ClassLoader classLoader = Main.class.getClassLoader();
        while (classLoader != null) {
            System.out.println(classLoader);
            classLoader = classLoader.getParent();
        }
        System.out.println(classLoader);
    }
}
1
2
3
4
5
6
7
8
9
10

image-20201212212341470

我们在尝试被Bootstarp ClassLoader类所加载的ClassLoader的时候都会返回null,具体原因下文会解释。

# 到源码中看看

我们前面说到App ClassLoader的父加载器是Extension ClassLoader。对于这一点,如果你有足够的好奇心想知道为什么的话,就可以到这两个类的源代码中去看一看。

这两个类都是sun.misc.Launcher的内部类,我们需要注意的是这几行代码:

image-20201219155836643

var1是一个Extension ClassLoader,作为一个参数传入了getAppClassLoader方法中,在getAppClassLoader中又调用了AppClassLoader的构造方法(这种设计方式叫单例模式,目的是为了保证只有一份类的实例):

image-20201219160314264

var0就是上一步中得到的Extension ClassLoader。AppClassLoader是URLClassLoader的子类,URLClassLoader间接继承于ClassLoader,最后一步步调用到ClassLoader的构造方法,把var0作为parent来传入:

image-20201219160841299

image-20201219160856461

image-20201219160908002

至于Extension ClassLoader并没有对其parent的明确赋值,而是在构造方法中传入了null:

image-20201219161345689

但是它的父加载器却是BootStrap ClassLoader,BootStrap ClassLoader逻辑采用C++编写,它是JVM的一部分,本身并不是一个Java类,也就无法获得它的引用。Java的一些核心类,比如java.lang.String、java.lang.Integar等都有它来加载。它没有父加载器,但是它可以作用于一个父加载器。

# 几个重要方法

ClassLoader#loadClass这个方法会根据全类名(即包名.类名)来加载类,它的执行过程一般会经历以下几个阶段:

  1. findLoadedClass():检测待加载的类是否已经被JVM加载过,如果是,则直接返回该类。
  2. parent.loadClass():如果没有加载到类的话,则会使用双亲委派机制来递归调用loadClass来加载类。
  3. findClass():如果还是没有找到,则会使用findClass来寻找类,如果找到的话,然后调用defineClass()来向JVM中注册该类。

源码如下:

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

findClass方法中明确了一些规则来告诉Java如何寻找的要加载的类,我们可以写一个自己的类加载器来自定义这个规则。

# 自定义加载器

一个自定义加载器一般来说需要满足以下几点:

  • 继承于ClassLoader
  • 自定义寻找类的规则,也就是重写findClass方法
  • 在findClass方法中调用defineClass方法向JVM中注册类

我们下面开始尝试编写一个自定义的ClassLoader,并且定义它的规则就是根据.class文件的存放路径来加载类。

首先用javassist编写一个简单的待加载的类Hello,生成的.class文件反编译之后如下所示:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

public class Hello {
    static {
        System.out.println("Hello ClassLoader");
    }

    public Hello() {
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14

然后编写ClassLoader:

package classloader;

import java.io.*;

public class CustomClassLoader extends ClassLoader {
    private final String classpath;

    public CustomClassLoader() {
        classpath = "./";
    }
    public CustomClassLoader(String classpath) {
        this.classpath = classpath;
    }


    private String getClassNameWithPaths(String name) {
        if (name.endsWith(".class")) {
            return name;
        }
        int index = name.lastIndexOf(".");
        if (index == -1) {
            return name + ".class";
        } else {
            return name.substring(index + 1) + ".class";
        }

    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        String className = getClassNameWithPaths(name);
        File file = new File(classpath, className);
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

            int length = 0;
            byte[] buffer = new byte[1024];

            try {
                while ((length = fileInputStream.read(buffer)) != -1) {
                    byteArrayOutputStream.write(buffer, 0, length);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            byte[] bytes = byteArrayOutputStream.toByteArray();

            try {
                byteArrayOutputStream.close();
                fileInputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return defineClass(name, bytes, 0, bytes.length);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        return super.findClass(name);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

实现的原理也很简单,就是用Java常规的IO操作来读取字节码文件的内容,然后将其转化为字节数组,最后由defineClass向JVM中注册该类。

package classloader;

public class Main {
    public static void main(String[] args) throws Exception {
        CustomClassLoader customClassLoader = new CustomClassLoader("D:\\desktop\\JacksonTest");
        Class<?> hello = customClassLoader.loadClass("Hello");
        System.out.println(hello.newInstance());
    }
}
1
2
3
4
5
6
7
8
9

运行结果:

image-20201219174144269

# 动态加载

Java类的加载可以被分为显示加载和隐式加载两种方式,所谓隐式加载就是直接使用new关键字来创建一个类的实例,而显示加载又可以称作动态加载,可以使用反射或者ClassLoader来动态加载类对象。

// 反射
java.lang.Class.forName("ClassName")
    
// 使用ClassLoader
{ClassLoader}.loadClass()
1
2
3
4
5
package test;

class Student {
    public String name;
    public int age;
}

public class Main {
    public static void main(String[] args) throws Exception {
        Student student = new Student();
        System.out.println("-------Load Class by ClassLoader------");
        ClassLoader classLoader = student.getClass().getClassLoader();
        System.out.println(classLoader);
        Class<?> Class = classLoader.loadClass("test.Student");
        System.out.println(Class);

        System.out.println("------Load Class by Reflection------");
        java.lang.Class<?> ClassRef = java.lang.Class.forName("test.Student");
        System.out.println(ClassRef);

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

image-20201212174946199

在Student student = new Student()这条语句执行的时候Student类已经被注册到JVM中了,所以我们用AppClassLoader#loadClass可以直接获取到类。

如果使用Class.forName("ClassName")来加载类的话还会触发类的static代码块,其实它有两种形式:

Class.forname(String className)
Class.forName(String name,boolean initialize, ClassLoader loader)
1
2

这一点在反射中已经学习过了。

# 使用Classloader直接加载字节码

通过ClassLoader#loadClass(String className)这样使用类名来加载类的时候(默认该类没有被JVM加载过)要经历下面三个方法的调用:

graph LR

loadClass-->findClass-->defineClass
1
2
3
  • loadClass:根据类名使用双亲委派机制从父加载器中寻找类,如果没找到会调用findClass
  • findClass:会根据URL所指定的方式来寻找字节码,然后调用defineClass处理字节码
  • defineClass:把Java字节码处理成真正的Java类,并且在JVM中注册

所以加载类的核心部分是最后的defineClass,我们可以写一个Demo来演示如何使用defineClass来直接加载Java字节码:

package test;

import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Method;

public class DefineClassTest {
    public static void main(String[] args) throws Exception {
        // 生成一个Java字节码文件
        ClassPool pool = ClassPool.getDefault();
        CtClass test = pool.makeClass("Test");
        String cmd = "java.lang.System.out.println(\"Hello Test\");";
        test.makeClassInitializer().insertBefore(cmd);
        byte[] bytes = test.toBytecode();

        // 由于defineClass默认属性是protected,所以要用反射的方法来获取该方法
        Class<?> Clazz = Class.forName("java.lang.ClassLoader");
        Method defineClass = Clazz.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defineClass.setAccessible(true);

        Class test1 = (Class) defineClass.invoke(ClassLoader.getSystemClassLoader(), "Test", bytes, 0, bytes.length);
        test1.newInstance();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

image-20201212230057034

获取到一个Java字节码文件之后,可以通过ClassLoader#defineClass来把它变成真正的Java类。但是在defineClass执行的时候并不会触发static代码块或者类的构造方法的,只有当显式调用其构造函数的时候才会被执行。因为ClassLoader#defineClass的属性是protected,所以无法直接在类外部访问,在实际情况中很少直接利用这种方式。

但是com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中的内部类TransletClassLoader重写了defineClass方法:

static final class TransletClassLoader extends ClassLoader {
    
······
    
    Class defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
        }
    
······
    
}
1
2
3
4
5
6
7
8
9
10
11

并且这个方法没有声明作用域,默认为default,可以在类外部被调用,所以就有了下面这条链:

graph LR

TemplatesImpl#getOutputProperties --> TemplatesImpl#newTransformer -->
TemplatesImpl#getTransletInstance --> TemplatesImpl#defineTransletClasses
--> TransletClassLoader#defineClass

1
2
3
4
5
6

调试过CC的同学肯定非常熟悉这条利用链,另外在Fastjson和Jackson等反序列化漏洞中都有它的身影。

另外TemplatesImpl中对于加载的字节码有一定的要求:这个字节码所对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。

# 使用URLClassLoader远程加载字节码

java.net.URLClassLoader,继承于ClassLoader并且是App ClassLoader的父类,它通过重写findClass方法来实现了加载目录系统中的.class文件或者是远程服务器上的.class文件的功能。

image-20201218164356546

注意继承关系和双亲委派机制中的父加载器的区别:URLClassLoader虽然是App ClassLoader的父类,但是在默认情况下它的父加载器却是App ClassLoader:

package classloader;

import java.net.URL;
import java.net.URLClassLoader;

public class Main {
    public static void main(String[] args) throws Exception {
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);

        URL url = new URL("http://127.0.0.1:8080");
        URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{url});
        System.out.println(urlClassLoader);
        System.out.println(urlClassLoader.getParent());

    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

image-20201214164157958

它可以让我们通过下面几种方式来加载.class文件:

  • 从Jar包中加载
  • 从文件系统目录中加载
  • 从远程服务器进行加载

每种加载方式都对应着一种格式的URL(也叫做基础路径),这里我直接引用P牛文章中的部分内容:

正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举的基础路径(这些路径是经过处理之后的java.net.URL类)来寻找.class文件来加载,这种基础路径分为三种情况:

  • URL未以/结尾,则认为是一个JAR文件,用JarLoader来寻找类,即在Jar包中寻找.class文件
  • URL以/结尾,且协议名是file,则用FileLoader来寻找类,即在本地文件系统中寻找.class文件
  • URL以/结尾,且协议名不是file,则使用最基础的Loader来寻找类

最常见的一种情况就是可以使用http协议来加载远程服务器上的.class文件,我们可以写一个Demo:

  1. 编写一个Hello.java文件,然后将其编译成.class文件,最后开启http服务将其托管
public class Hello {
    public Hello() {
        System.out.println("Say Hello");
    }
}
1
2
3
4
5

image-20201212143527293

  1. 再使用URLClassLoader去加载.class文件
import java.net.URL;
import java.net.URLClassLoader;

public class LoaderTest {
    public static void main(String[] args) throws Exception {
        URL[] urls = {new URL("http://127.0.0.1:8080/")};
        URLClassLoader urlClassLoader = URLClassLoader.newInstance(urls);
        Class<?> helloClass = urlClassLoader.loadClass("Hello");
        helloClass.newInstance();
    }
}
1
2
3
4
5
6
7
8
9
10
11

image-20201212143652395

所以,如果可以控制ClassLoader的加载类的基础路径的话,则可以使用远程加载恶意类的方式来执行任意代码。

# 使用Unsafe直接加载字节码

除了ClassLoader#defineClass可以直接加载字节码之外,Java还提供给我们一个类sun.misc.Unsafe来实现同样的功能。这个类提供了对于底层的内存、线程、类、对象等操作的方法,其中Unsafe#defineClass就可以直接向JVM中注册类、实现直接加载字节码的功能。

package test.unsafe;

import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws Exception {
        // 生成一个Java字节码文件
        ClassPool pool = ClassPool.getDefault();
        CtClass test = pool.makeClass("Test");
        String cmd = "java.lang.System.out.println(\"Hello Test\");";
        test.makeClassInitializer().insertBefore(cmd);
        byte[] bytes = test.toBytecode();

        // 利用反射创建Unsafe对象
        Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Unsafe unsafe = constructor.newInstance();
        System.out.println(unsafe);

        // 调用Unsafe#defineClass
        Class aClass = unsafe.defineClass("Test", bytes, 0, bytes.length);
        aClass.newInstance();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

image-20201213022103298

上面的方法完整定义为public native Class defineClass(String var1, byte[] var2, int var3, int var4);,这个方法仅适用于Java8之前的版本,在Java8中这个方法的定义变成了public native Class<?> defineClass(String var1, byte[] var2, int var3, int var4, ClassLoader var5, ProtectionDomain var6);,在使用的时候需要传入一个ClassLoader和ProtectionDomain,下面是Java8版本中的示例:

package test.unsafe;

import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;

public class Main {
    public static void main(String[] args) throws Exception {
        // 生成一个Java字节码文件
        ClassPool pool = ClassPool.getDefault();
        CtClass test = pool.makeClass("Test");
        String cmd = "java.lang.System.out.println(\"Hello Test\");";
        test.makeClassInitializer().insertBefore(cmd);
        byte[] bytes = test.toBytecode();

        // 利用反射创建Unsafe对象
        Constructor<Unsafe> constructor = Unsafe.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        Unsafe unsafe = constructor.newInstance();
        System.out.println(unsafe);

        // Java8中调用Unsafe#defineClass
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, systemClassLoader, null);
        Class aClass = unsafe.defineClass("Test", bytes, 0, bytes.length, systemClassLoader, protectionDomain);
        aClass.newInstance();
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

image-20201213022953539

在Java11中Unsafe#defineClass方法已经被移除,也就不能再使用Unsafe类来直接加载字节码了,且用且珍惜。

# 使用Proxy来直接加载字节码

java.lang.reflect.Proxy是Java动态代理中常用的一个类,但是它有个defineClass0的native方法,也可以和ClassLoader和Unsafe类一样直接加载字节码。

image-20201214204423469

package test.proxy;

import javassist.ClassPool;
import javassist.CtClass;

import java.lang.reflect.Method;

public class Demo1 {
    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass hello = pool.makeClass("Hello");
        String command = "System.out.println('Hello Proxy');".replace("'", "\"");
        hello.makeClassInitializer().insertBefore(command);
        byte[] bytes = hello.toBytecode();

        Method defineClass0 = Class.forName("java.lang.reflect.Proxy").getDeclaredMethod("defineClass0", ClassLoader.class, String.class, byte[].class, int.class, int.class);
        defineClass0.setAccessible(true);
        Class helloClass = (Class) defineClass0.invoke(null, ClassLoader.getSystemClassLoader(), "Hello", bytes, 0, bytes.length);
        Object helloClassInstance = helloClass.newInstance();
        System.out.println(helloClass);
        System.out.println(helloClassInstance);
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

image-20201214204941267

# 使用BCEL来加载字节码

  • 参考:BCEL ClassLoader去哪了 | 离别歌 (leavesongs.com)

  • 包含在原生JDK中,但在8u251之后之后的BCEL的ClassLoader被移除了

BCEL提供了两个类Repository和Utility:

  • Repository用于将一个Java类转换成Java原生字节码,也可以使用javac来编译.java文件来获取字节码
  • Utility用于将Java原生字节码来转化成BCEL格式的字节码

在ClassLoader#loadClass中会判断类名是否已$$BCEL$$开头,如果是的话会对这个字符串进行decode。

Demo如下,先编写一个恶意类Evil,它的static{}中会触发计算器:

import java.io.IOException;

public class Evil {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

然后使用BCEL中的类把这个类转化为符合BCEL格式的字节码,最后进行loadClass:

import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;

public class BCELTest {
    public static void main(String[] args) throws Exception {
        JavaClass javaClass = Repository.lookupClass(Evil.class);
        String codes = Utility.encode(javaClass.getBytes(), true);
        System.out.println(codes);

        ClassLoader loader = new ClassLoader();
        loader.loadClass("$$BCEL$$" + codes).newInstance();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

image-20201219130540936

# 参考

  • javasec.org(现在已经被授权到知识盒子,需要付费查看)
  • P牛《Java安全漫谈-13.Java中动态加载字节的那些方法》(知识星球,同样需要付费)
  • URLClassLoader类加载器_赶路人儿-CSDN博客
  • 详细深入分析 Java ClassLoader 工作机制_http://www.54tianzhisheng.cn/-CSDN博客
Fastjson(2)-TemplatesImpl利用链
绕过CSP的一些方法

← Fastjson(2)-TemplatesImpl利用链 绕过CSP的一些方法→

最近更新
01
Java8-Stream
01-03
02
Fastjson反序列化(2)-TemplatesImpl利用链
12-01
03
Fastjson反序列化(1)-初探利用方式
11-30
更多文章>
Theme by Vdoing | Copyright © 2019-2021
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式