记录一下最近学习的go下的ssti漏洞问题
前言
作为强类型的静态语言,golang的安全属性从编译过程就能够避免大多数安全问题,一般来说也唯有依赖库和开发者自己所编写的操作漏洞,才有可能形成漏洞利用点,在本文,主要学习探讨一下golang的一些ssti模板注入问题
GO模板引擎
Go 提供了两个模板包。一个是 text/template
,另一个是html/template
。text/template对 XSS 或任何类型的 HTML 编码都没有保护,因此该模板并不适合构建 Web 应用程序,而html/template与text/template基本相同,但增加了HTML编码等安全保护,更加适用于构建web应用程序
template简介
template之所以称作为模板的原因就是其由静态内容和动态内容所组成,可以根据动态内容的变化而生成不同的内容信息交由客户端,以下即一个简单例子
1 | 模板内容 Hello, {{.Name}} Welcome to go web programming… |
而作为go所提供的模板包,text/template和html/template的主要区别就在于对于特殊字符的转义与转义函数的不同,但其原理基本一致,均是动静态内容结合,以下是两种模板的简单演示
text/template
1 | package main |
struct是定义了的一个结构体,在go中,我们是通过结构体来类比一个对象,因此他的字段就是一个对象的属性,在该实例中,我们所期待的输出内容为下
1 | 模板内容 <h1>Hi, {{ .Name }}</h1><br>Your Email is {{ .Email }} |
可以看得出来,当传入参数可控时,就会经过动态内容生成不同的内容,而我们又可以知道,go模板是提供字符串打印功能的,我们就有机会实现xss
1 | package main |
1 | 模板内容 <h1>Hi, {{"<script>alert(/xss/)</script>"}}</h1><br>Your Email is {{ .Email }} |
这里就是text/template和html/template的最大不同了
html/template
同样的例子,但是我们把导入的模板包变成html/template
1 | package main |
可以看到,xss语句已经被转义实体化了,因此对于html/template来说,传入的script和js都会被转义,很好地防范了xss,但text/template也提供了内置函数html来转义特殊字符,除此之外还有js,也存在template.HTMLEscapeString
等转义函数
而通过html/template包等,go提供了诸如Parse/ParseFiles/Execute等方法可以从字符串或者文件加载模板然后注入数据形成最终要显示的结果
html/template
包会做一些编码来帮助防止代码注入,而且这种编码方式是上下文相关的,这意味着它可以发生在 HTML、CSS、JavaScript 甚至 URL 中,模板库将确定如何正确编码文本
template常用基本语法
在{{}}
内的操作称之为pipeline
1 | {{.}} 表示当前对象,如user对象 |
漏洞演示
在go中检测 SSTI 并不像发送 49 并在源代码中检查 49 那么简单,我们需要浏览文档以查找仅 Go 原生模板中的行为,最常见的就是占位符.
在template中,点”.”代表当前作用域的当前对象,它类似于java/c++的this关键字,类似于perl/python的self
1 | package main |
输出为
1 | 模板内容 <h1>Hi, {{ .Name }}</h1><br>Your Email is {{ . }} |
可以看到结构体内的都会被打印出来,我们也常常利用这个检测是否存在SSTI
接下来就以几道题目来验证一下
[LineCTF2022]gotm
1 | package main |
我们先对几个路由和其对应的函数进行分析
struct结构
1 | type Account struct { |
注册功能
1 | func regist_handler(w http.ResponseWriter, r *http.Request) { |
登录功能
1 | func auth_handler(w http.ResponseWriter, r *http.Request) { |
认证功能
1 | func root_handler(w http.ResponseWriter, r *http.Request) { |
得到account
1 | func get_account(uid string) Account { |
flag路由
1 | func flag_handler(w http.ResponseWriter, r *http.Request) { |
所以思路就清晰了,我们需要得到secret_key,然后继续jwt伪造得到flag
而由于root_handler
函数中得到的acc是数组中的地址,即会在全局变量acc函数中查找我们的用户,这时传入{{.secret_key}}
会返回空,所以我们用{{.}}
来得到结构体内所有内容
1 | /regist?id={{.}}&pw=123 |
1 | /auth?id={{.}}&pw=123 |
1 | {"status":true,"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOmZhbHNlfQ.0Lz_3fTyhGxWGwZnw3hM_5TzDfrk0oULzLWF4rRfMss"} |
带上token重新访问
1 | Logged in as {{{.}} 123 false this_is_f4Ke_key} |
得到secret_key,进行jwt伪造,把 is_admin
修改为true,key填上secret_key得到
1 | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOnRydWV9.3OXFk-f_S2XqPdzHnl0esmJQXuTSXuA1IbpaGOMyvWo |
带上token访问/flag
[WeCTF2022]request-bin
洁白一片,使用{{.}}
进行检测
这道题目采用的框架是iris,用户可以对日志的格式参数进行控制,而参数又会被当成模板渲染,所以我们就可以利用该点进行ssti
我们需要的是进行文件的读取,所以我们需要看看iris
的accesslog
库的模板注入如何利用
在Accesslog的结构体中可以发现
1 | type Log struct { |
这里我们经过审查,会发现context里面存在SendFile进行文件强制下载
所以我们可以构造payload如下
1 | {{ .Ctx.SendFile "/flag" "1.txt"}} |
后言
golang的template跟很多模板引擎的语法差不多,比如双花括号指定可解析的对象,假如我们传入的参数是可解析的,就有可能造成泄露,其本质就是合并替换,而常用的检测payload可以用占位符.
,对于该漏洞的防御也是多注意对传入参数的控制