羊城杯 Checkin_Go
拿到源码是一个用golang写的gin应用,gin是一个在golang中常用的web框架。
只能在本地搭建好环境才能解出来,最开始搭建golang的环境其实是比较烦的,因为一直在写golang,所以我的环境是在很久之前就已经搭建好了的。配置好go mod后直接在web目录下go run .
即可运行。
直接看一下源码吧,在登陆的地方:
注意可以在本地吧hashProofRequired()里面爆破md5的代码删掉,就不用每次填hashcode了
func hashProofRequired() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
}
}
func loginPostHandler(c *gin.Context) {
uname := c.PostForm("uname")
pwd := c.PostForm("pwd")
if uname == "admin"{
c.String(200,"noon,you cant be admin")
return
}
if uname == "" || pwd == "" {
c.String(200, "empty parameter")
return
}
s := sessions.Default(c)
s.Set("uname", uname)
s.Save()
c.Redirect(302, "/game")
}
可以看到通过登陆的过程是不可能成为admin的。
再看看/game路由的逻辑:
直接购买flag的逻辑如下
if u1 >= u2 && checkPlayerMoney == playerMoney && checkNowMoney == nowMoney {
newMoney = uint32(u1) - uint32(u2)
f, err := ioutil.ReadFile("flag")
if err == nil {
s.Set("playerMoney", newMoney)
s.Set("checkPlayerMoney", AesEncrypt(fmt.Sprintf("%v", s.Get("playerMoney")), string(secret)))
s.Save()
c.String(200, string(f))
c.Abort()
return
} else {
c.String(200, "SomethingWrong")
}
}
要满足以下条件:
- 余额大于等于flag的金额
- 余额的AES值等于check你的余额的值
- flag金额的AES值等于checkflag金额的值
并且add flag的时候,会首先验证你是否为admin。
多抓一下包,发现我们不管是登陆还是购买还是add操作,基本上没有可控的地方。
那么我们很容易想到也是比较关键的一个点就是控制cookie。
Cookie: o=MTYzMTQzNDc5MXxEdi1CQkFFQ180SUFBUkFCRUFBQV85Yl9nZ0FGQm5OMGNtbHVad3dOQUF0d2JHRjVaWEpOYjI1bGVRTnBiblFFQkFELUp4QUdjM1J5YVc1bkRCSUFFR05vWldOclVHeGhlV1Z5VFc5dVpYa0djM1J5YVc1bkRCZ0FGazl0U0ZFNGNFSXlaR2wzV0V0NE0xSk5hRXBLVlhjR2MzUnlhVzVuREFVQUEyaHphQVp6ZEhKcGJtY01DQUFHTkRnMVkyRTJCbk4wY21sdVp3d0tBQWh1YjNkTmIyNWxlUU5wYm5RRUJRRDlCaHFBQm5OMGNtbHVad3dQQUExamFHVmphMDV2ZDAxdmJtVjVCbk4wY21sdVp3d1lBQlpLYTJWTVRuTXdkRUZ1WnpkeVJHUm5kSEl4YmtSUnxVeH6dJkg3e_CXF5CCPlhl58UcwV9bkj9RumM1WNrCMw==
对应cookie生成的代码:
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
storage := cookie.NewStore(randomChar(16))
r.Use(sessions.Sessions("o", storage))
这里看到是使用了sessions.Sessions
来生成cookie,那么我们得找到源码里面是怎么解密cookie的
从代码的位置一直跟进一下代码,自己眼睛放尖一些,以及在Goland里面全局(scoup)里搜索一下decode/encode等关键字
找到~\go\pkg\mod\github.com\gorilla\securecookie@v1.1.1\securecookie.go
里面
// DecodeMulti decodes a cookie value using a group of codecs.
//
// The codecs are tried in order. Multiple codecs are accepted to allow
// key rotation.
//
// On error, may return a MultiError.
func DecodeMulti(name string, value string, dst interface{}, codecs ...Codec) error {
if len(codecs) == 0 {
return errNoCodecs
}
var errors MultiError
for _, codec := range codecs {
err := codec.Decode(name, value, dst)
if err == nil {
return nil
}
errors = append(errors, err)
}
return errors
}
可以在中间插入一句fmt.Println("Decoded cookie is : " + value)
试试看我们的猜想以及找的位置对不对。
cookie位置找对了,发现没有被解密开,那我们回到上层调用的地方试试,找到:~\go\pkg\mod\github.com\gorilla\sessions@v1.1.3\store.go
// New returns a session for the given name without adding it to the registry.
//
// The difference between New() and Get() is that calling New() twice will
// decode the session data twice, while Get() registers and reuses the same
// decoded session after the first call.
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
session := NewSession(s, name)
opts := *s.Options
session.Options = &opts
session.IsNew = true
var err error
if c, errCookie := r.Cookie(name); errCookie == nil {
err = securecookie.DecodeMulti(name, c.Value, &session.Values,
s.Codecs...)
fmt.Println(session.Values)
if err == nil {
session.IsNew = false
}
}
return session, err
}
接下来cookie的内容也看到了,类型是field Values map[interface{}]interface{} of Session type
仔细看一下解出来的Cookie
map[checkNowMoney:JkeLNs0tAng7rDdgtr1nDQ checkPlayerMoney:OmHQ8pB2diwXKx3RMhJJUw hsh:0397ba nowMoney:200000 playerMoney:5000 uname:a]
可以看到储存了我们需要的checkMoney和对应的Money,然后还有uname的值,再回到我们的逻辑,
if u1 >= u2 && checkPlayerMoney == playerMoney && checkNowMoney == nowMoney {
//"cat /flag"
再注意一下我们的Money:
newMoney = uint32(u1) - uint32(u2)
uint32,很明显的整数溢出了。
这样来看,只要我们能把用户名伪造成admin,然后利用整数溢出把flag越加越小,然后买flag就行了!
先题目服务器上随便登陆一个用户,拿到cookie:
o = MTYzMTQ1Mjc0N3xEdi1CQkFFQ180SUFBUkFCRUFBQV9fZl9nZ0FHQm5OMGNtbHVad3dQQUExamFHVmphMDV2ZDAxdmJtVjVCbk4wY21sdVp3d1lBQlpLYTJWTVRuTXdkRUZ1WnpkeVJHUm5kSEl4YmtSUkJuTjBjbWx1Wnd3TkFBdHdiR0Y1WlhKTmIyNWxlUU5wYm5RRUJBRC1KeEFHYzNSeWFXNW5EQklBRUdOb1pXTnJVR3hoZVdWeVRXOXVaWGtHYzNSeWFXNW5EQmdBRms5dFNGRTRjRUl5WkdsM1dFdDRNMUpOYUVwS1ZYY0djM1J5YVc1bkRBY0FCWFZ1WVcxbEJuTjBjbWx1Wnd3SUFBWmhZV0ppWTJNR2MzUnlhVzVuREFVQUEyaHphQVp6ZEhKcGJtY01DQUFHTXpaalkySTJCbk4wY21sdVp3d0tBQWh1YjNkTmIyNWxlUU5wYm5RRUJRRDlCaHFBfCXjrBCgFu_2DL6yKfRrRK-ptVS1OFMDp7jOCZbqRrGB
放到源码里面解密一下:
map[checkNowMoney:JkeLNs0tAng7rDdgtr1nDQ checkPlayerMoney:OmHQ8pB2diwXKx3RMhJJUw hsh:36ccb
6 nowMoney:200000 playerMoney:5000 uname:aabbcc]
然后尝试修改一下uname的值,保持其他字段与服务器上的一致,并且打印新的cookie:
得到新的cookie:
MTYzMTQ1MzUyOHxEdi1CQkFFQ180SUFBUkFCRUFBQV9fYl9nZ0FHQm5OMGNtbHVad3dQQUExamFHVmphMDV2ZDAxdmJtVjVCbk4wY21sdVp3d1lBQlpLYTJWTVRuTXdkRUZ1WnpkeVJHUm5kSEl4YmtSUkJuTjBjbWx1Wnd3TkFBdHdiR0Y1WlhKTmIyNWxlUU5wYm5RRUJBRC1KeEFHYzNSeWFXNW5EQklBRUdOb1pXTnJVR3hoZVdWeVRXOXVaWGtHYzNSeWFXNW5EQmdBRms5dFNGRTRjRUl5WkdsM1dFdDRNMUpOYUVwS1ZYY0djM1J5YVc1bkRBY0FCWFZ1WVcxbEJuTjBjbWx1Wnd3SEFBVmhaRzFwYmdaemRISnBibWNNQlFBRGFITm9Cbk4wY21sdVp3d0lBQVkyTmpjME5qUUdjM1J5YVc1bkRBb0FDRzV2ZDAxdmJtVjVBMmx1ZEFRRkFQMEdHb0E9fKSjL3VXDEsKcInYkSbhU2oyJvjDK_hn85iR_KfCjMTE
然后在服务器上替换一下,我们就可以正常的add flag的金额了。
接下来整数溢出就比较简单了
Uint32的范围是:[0 : 4294967295]
然后你需要加的钱就是:4294967295 – 你当前的钱 + 1
就可以清0然后买flag了:
总结
- golang框架源码
- cookie伪造
- 整数溢出
记得把源码改回来,不然后面写的项目可能会崩掉哈哈哈哈