[LineCTF2022]gotm复现
开题前自我的概述:
最近要出道go题(之前都没写过,只是听说过连go语言还没学过),于是找了个go ssti题来学学,到底go 题的流程是怎么样的
参考博客:[LineCTF2022]gotm | 北歌(北歌师傅)
复现平台:BUUCTF
下载好附件,我们开始代码审计
包含这些文件
其中
Dockerfile和run.sh还有go.sum,go.mod 是配置相关的文件
go代码审计主要审计的是main.go 文件
go代码审计
功能模块分析
这段 Go 代码的作用是导入声明表明
1 | package main |
有个Account结构体,主要四个属性
1 | type Account struct { |
路由分析
有四个路由
1 | func main() { |
我们一个一个路由来审计
/路由
我们先来看”/“路由
1 | func root_handler(w http.ResponseWriter, r *http.Request) { |
先获取Token,如果有Token则用jwt解密,如果解密成功则显示用户的id,如果没有token直接返回空白
/auth路由
然后审/auth
用户登录部分
结构体部分
1 | type TokenResp struct { |
1 | uid := r.FormValue("id") |
获取用户传入的id和pw
1 | user_acc := get_account(uid) |
理解并分析一下代码:
如果我们输入了正确的id和pw(get_account有匹配uid的正确结果),
返回一个TokenResp的对象(json形式),里面存储了状态status,和jwt token,内容是id和是否为admin
/flag路由
然后我们审计“/flag”路由
1 | func flag_handler(w http.ResponseWriter, r *http.Request) { |
通过对传入的X-Token进行jwt解码,然后判断is_admin,如果为true就给flag
/regist路由
最后来看/regist路由
1 | func regist_handler(w http.ResponseWriter, r *http.Request) { |
1 | 由这条语句:Account{uid, upw, false, secret_key} |
路由中默认给is_admin为false,所以我们要想办法给is_admin为true
由于jwt编码过程中需要用到secret_key作签名密钥,secret_key是环境变量,就需要通过SSTI漏洞把secret_key
代码审计分析到此结束
做题分析
现在我们要利用go ssti注入获取key然后伪造jwt
go ssti 参考:https://forum.butian.net/share/1286
1 | 先说一下go的ssti,和jinja2的ssti类似,都是因为直接渲染拼接的字符导致插入了模板语言后执行 |
这道题的ssti漏洞产生的位置在
http.HandleFunc(“/“, root_handler)
1 | func root_handler(w http.ResponseWriter, r *http.Request) { |
常规思路可以注入{{.}}
或{{.secret_key}}
来读secret_key属性,但此处由于root_handler()
函数得到的acc是数组中的地址,也就是get_account函数通过在全局变量acc数组中查找我们的用户,这种情况下直接注入{{.secret_key}}
会返回空,所以此处只能用{{.}}
来返回全部属性
所以我们现在要去注册界面
访问我们在/regist页面进行注册
出现回显
1 | http://9aa8cdd8-b760-441f-8860-f4275dc92ec0.node5.buuoj.cn:81/regist?id={{.}}&pw=pass |
注册完之后我们开始去登录
然后我们访问/auth进行登录,得到token
1 | http://9aa8cdd8-b760-441f-8860-f4275dc92ec0.node5.buuoj.cn:81/auth?id={{.}}&pw=pass |
我们再回到最开始的页面,将token值传入X-Token头部,成功读到secret_key
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.0Lz_3fTyhGxWGwZnw3hM_5TzDfrk0oULzLWF4rRfMss |
1 | http://9aa8cdd8-b760-441f-8860-f4275dc92ec0.node5.buuoj.cn:81 |
这样我们就得到了关键的secret_key
1 | this_is_f4Ke_key |
修改jwt
我们修改jwt,将is_admin修改为true. (jwt 加密解密网站jwt解密/加密 - bejson在线工具)
先把原来的jwt复制过来解码
之后我们再放入密钥,改变值为true
然后点击编码
复制jwt
再到/flag页面,将修改后的jwt传入token中
得到flag
总结:
go 类型的题目需要代码审计来理解
需要仔细分析每个路由的作用
jwt 加密解密需掌握
[2022DASCTF MAY 出题人挑战赛]fxxkgo复现
开题前的概述: 前面已经复现了一道go([LineCTF2022]gotm)题,看看这道题分析分析复现看看
(go ssti + jwt伪造)
复现平台:BUUCTF
下载好附件后,我们开始代码审计
有这些文件 .idea 里面是一些配置文件,Dockerfile 是部署文件,go.mod 和go sum 是项目依赖文件
我们主要是看main.go
go代码审计
main.go内容
功能模块分析
这段 Go 代码的作用是导入声明表明
1 | package main |
1 | type Account struct { |
定义了 Account 结构体,有四个属性
路由分析
1 | func main() { |
根据主函数我们可以得知它有5个路由
根目录的GET
请求和POST
请求进入的路由不相同,实现的功能也不同。这个题目其实就是按照路由走一遍然后就可以得到lag
了,具体怎么走,看的懂go语言
的兄弟可以思考一下,看不懂的我直接讲了,首先我们先去分析五个路由对应的函数实现了什么功能
一个一个来分析
/路由(GET)
1 | func index(c *gin.Context) { |
功能:进入之后输出json格式的信息
/路由(POST)
1 | func rootHandler(c *gin.Context) { |
先获取Token,如果有Token则用jwt解密,如果解密成功则显示用户的id,如果没有token直接返回空白
flag路由
1 | unc flagHandler(c *gin.Context) { |
通过对传入的X-Token进行jwt解码,然后判断is_admin,如果为true就给flag
auth路由
1 | func authHandler(c *gin.Context) { |
获取用户传入的id和pw,
理解并分析一下代码:
如果我们输入了正确的id和pw(get_account有匹配uid的正确结果),
返回一个TokenResp的对象(json形式),里面存储了状态status,和jwt token,内容是id和是否为admin
register 路由
1 | func Resist(c *gin.Context){ |
Account{uid, upw, false, secret_key}
路由中默认给is_admin为false,所以我们要想办法给is_admin为true
由于jwt编码过程中需要用到secret_key作签名密钥,secret_key是环境变量,就需要通过SSTI漏洞把secret_key
代码审计分析到此结束
做题分析
现在我们要利用go ssti注入获取key然后伪造jwt
go ssti 参考:https://forum.butian.net/share/1286
1 | 先说一下go的ssti,和jinja2的ssti类似,都是因为直接渲染拼接的字符导致插入了模板语言后执行 |
这道题的ssti漏洞产生的位置在
r.POST(“/“, rootHandler)
1 | func rootHandler(c *gin.Context) { |
常规思路可以注入{{.}}
或{{.secret_key}}
来读secret_key属性,但此处由于root_handler()
函数得到的acc是数组中的地址,也就是get_account函数通过在全局变量acc数组中查找我们的用户,这种情况下直接注入{{.secret_key}}
会返回空,所以此处只能用{{.}}
来返回全部属性
所以我们现在要去注册界面
访问我们在/register页面进行注册
出现回显
1 | http://8d7710a9-008f-452c-80de-38d1aa7b90cb.node5.buuoj.cn:81/register |
注册完之后我们开始去登录
然后我们访问/auth进行登录,得到token
1 | http://8d7710a9-008f-452c-80de-38d1aa7b90cb.node5.buuoj.cn:81/auth |
我们再回到最开始的页面,将token值传入X-Token头部,成功读到secret_key
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.1c8I_PzGiyonSZe3UPM2AB94x07g6DeyJW6uYA2C7eo |
这样我们就得到了关键的secret_key
1 | fasdf972u1041xu90zm10Av |
修改jwt
我们修改jwt,将is_admin修改为true. (jwt 加密解密网站jwt解密/加密 - bejson在线工具)
先把原来的jwt复制过来解码
之后我们再放入密钥,改变值为true
然后点击编码
复制jwt
再到/flag路由页面,将修改后的jwt传入token中
总结:这道题和[LineCTF2022]gotm 高度相似,改了点代码(基本步骤都一样)
go语言代码审计得加强
jwt 加密解密基本掌握