您现在的位置是:IT资讯 >>正文
浅学Go下的Ssti漏洞问题
IT资讯36人已围观
简介前言作为强类型的静态语言,golang的安全属性从编译过程就能够避免大多数安全问题,一般来说也唯有依赖库和开发者自己所编写的操作漏洞,才有可能形成漏洞利用点,在本文,主要学习探讨一下golang的一些 ...
前言
作为强类型的浅学静态语言 ,golang的漏洞安全属性从编译过程就能够避免大多数安全问题,一般来说也唯有依赖库和开发者自己所编写的问题操作漏洞,才有可能形成漏洞利用点 ,浅学在本文,漏洞主要学习探讨一下golang的问题一些ssti模板注入问题。
GO模板引擎
Go 提供了两个模板包 。浅学一个是漏洞 text/template ,另一个是问题html/template。text/template对 XSS 或任何类型的浅学 HTML 编码都没有保护 ,免费模板因此该模板并不适合构建 Web 应用程序 ,漏洞而html/template与text/template基本相同,问题但增加了HTML编码等安全保护 ,浅学更加适用于构建web应用程序 。漏洞
template简介
template之所以称作为模板的问题原因就是其由静态内容和动态内容所组成,可以根据动态内容的变化而生成不同的内容信息交由客户端,以下即一个简单例子
复制模板内容 Hello, { { .Name}}Welcome to go web programming…
期待输出 Hello, liumiaocn Welcome to go web programming…1.2.而作为go所提供的模板包,text/template和html/template的主要区别就在于对于特殊字符的转义与转义函数的不同,高防服务器但其原理基本一致,均是动静态内容结合,以下是两种模板的简单演示 。
text/template复制package main
import ("net/http""text/template")type User struct { ID intName string
Email string
Password string
}func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{ 1,"John", "test@example.com", "test123"}r.ParseForm()tpl := `<h1>Hi, { { .Name }}</h1><br>Your Email is { { .Email }}`
data := map[string]string{ "Name": user.Name,"Email": user.Email,}html := template.Must(template.New("login").Parse(tpl))html.Execute(w, data)}func main() { server := http.Server{ Addr: "127.0.0.1:8888",}http.HandleFunc("/string", StringTpl2Exam)server.ListenAndServe()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.struct是定义了的一个结构体,在go中,我们是通过结构体来类比一个对象,因此他的字段就是一个对象的属性,云计算在该实例中,我们所期待的输出内容为下
复制模板内容 <h1>Hi, { { .Name }}</h1><br>Your Email is { { .Email }}期待输出 <h1>Hi, John</h1><br>Your Email is test@example.com1.2.
可以看得出来,当传入参数可控时,就会经过动态内容生成不同的内容 ,而我们又可以知道,go模板是提供字符串打印功能的,我们就有机会实现xss。
复制package main
import ("net/http""text/template")type User struct { ID intName string
Email string
Password string
}func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{ 1,"John", "test@example.com", "test123"}r.ParseForm()tpl := `<h1>Hi, { { "<script>alert(/xss/)</script>"}}</h1><br>Your Email is { { .Email }}`
data := map[string]string{ "Name": user.Name,"Email": user.Email,}html := template.Must(template.New("login").Parse(tpl))html.Execute(w, data)}func main() { server := http.Server{ Addr: "127.0.0.1:8888",}http.HandleFunc("/string", StringTpl2Exam)server.ListenAndServe()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33. 复制模板内容 <h1>Hi, { { "<script>alert(/xss/)</script>"}}</h1><br>Your Email is { { .Email }}期待输出 <h1>Hi, { { "<script>alert(/xss/)</script>"}}</h1><br>Your Email is test@example.com实际输出 弹出/xss/1.2.3.
这里就是text/template和html/template的最大不同了。
html/template同样的例子 ,亿华云但是我们把导入的模板包变成html/template
复制package main
import ("net/http""html/template")type User struct { ID intName string
Email string
Password string
}func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{ 1,"John", "test@example.com", "test123"}r.ParseForm()tpl := `<h1>Hi, { { "<script>alert(/xss/)</script>"}}</h1><br>Your Email is { { .Email }}`
data := map[string]string{ "Name": user.Name,"Email": user.Email,}html := template.Must(template.New("login").Parse(tpl))html.Execute(w, data)}func main() { server := http.Server{ Addr: "127.0.0.1:8888",}http.HandleFunc("/string", StringTpl2Exam)server.ListenAndServe()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.
可以看到 ,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
复制{ { .}}表示当前对象,如user对象
{ { .FieldName}}表示对象的某个字段
{ { range …}}{ { end}}go中for…range语法类似 ,循环
{ { with …}}{ { end}}当前对象的值,上下文
{ { if …}}{ { else}}{ { end}} go中的if-else语法类似 ,条件选择
{ { xxx | xxx}}左边的输出作为右边的建站模板输入
{ { template "navbar"}} 引入子模版1.2.3.4.5.6.7.8.9.10.11.12.13.漏洞演示
在go中检测 SSTI 并不像发送 { { 7*7}} 并在源代码中检查 49 那么简单,我们需要浏览文档以查找仅 Go 原生模板中的行为,最常见的就是占位符.
在template中,点"."代表当前作用域的当前对象,它类似于java/c++的this关键字 ,类似于perl/python的self 。
复制package main
import ("net/http""text/template")type User struct { ID intName string
Email string
Password string
}func StringTpl2Exam(w http.ResponseWriter, r *http.Request) { user := &User{ 1,"John", "test@example.com", "test123"}r.ParseForm()tpl := `<h1>Hi, { { .Name }}</h1><br>Your Email is { { . }}`
data := map[string]string{ "Name": user.Name,"Email": user.Email,}html := template.Must(template.New("login").Parse(tpl))html.Execute(w, data)}func main() { server := http.Server{ Addr: "127.0.0.1:8888",}http.HandleFunc("/string", StringTpl2Exam)server.ListenAndServe()}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.输出为
复制模板内容
Hi, ` `.`Name `
Your Email is ` `.` `期待输出 <h1>Hi, John</h1><br>Your Email is map[Email:test@example.com Name:John]1.2.可以看到结构体内的都会被打印出来 ,我们也常常利用这个检测是否存在SSTI。
接下来就以几道题目来验证一下
[LineCTF2022]gotm
复制package main
import ("encoding/json""fmt""log""net/http""os""text/template""github.com/golang-jwt/jwt")type Account struct {id string
pw string
is_admin boolsecret_key string
}type AccountClaims struct { Id string `json:"id"`
Is_admin bool `json:"is_admin"`
jwt.StandardClaims}type Resp struct { Status bool `json:"status"`
Msg string `json:"msg"`
}type TokenResp struct { Status bool `json:"status"`
Token string `json:"token"`
}var acc []Account
var secret_key = os.Getenv("KEY")var flag = os.Getenv("FLAG")var admin_id = os.Getenv("ADMIN_ID")var admin_pw = os.Getenv("ADMIN_PW")func clear_account() { acc = acc[:1]}func get_account(uid string) Account { for i := range acc { if acc[i].id == uid { return acc[i]}}return Account{ }}func jwt_encode(id string, is_admin bool) (string, error) { claims := AccountClaims{ id, is_admin, jwt.StandardClaims{ },}token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)return token.SignedString([]byte(secret_key))}func jwt_decode(s string) (string, bool) { token, err := jwt.ParseWithClaims(s, &AccountClaims{ }, func(token *jwt.Token) (interface{ }, error) { return []byte(secret_key),nil
})if err != nil { fmt.Println(err)return "", false}if claims, ok := token.Claims.(*AccountClaims); ok && token.Valid { return claims.Id, claims.Is_admin}return "", false}func auth_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id")upw := r.FormValue("pw")if uid == "" || upw == "" {return
}if len(acc) > 1024 { clear_account()}user_acc := get_account(uid)if user_acc.id != "" && user_acc.pw == upw { token, err := jwt_encode(user_acc.id, user_acc.is_admin)if err != nil {return
}p := TokenResp{ true, token}res, err := json.Marshal(p)if err != nil { }w.Write(res)return
}w.WriteHeader(http.StatusForbidden)return
}func regist_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id")upw := r.FormValue("pw")if uid == "" || upw == "" {return
}if get_account(uid).id != "" { w.WriteHeader(http.StatusForbidden)return
}if len(acc) > 4 { clear_account()}new_acc := Account{ uid, upw, false, secret_key}acc = append(acc, new_acc)p := Resp{ true, ""}res, err := json.Marshal(p)if err != nil { }w.Write(res)return
}func flag_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token")if token != "" { id, is_admin := jwt_decode(token)if is_admin == true { p := Resp{ true, "Hi " + id + ", flag is " + flag}res, err := json.Marshal(p)if err != nil { }w.Write(res)return
} else { w.WriteHeader(http.StatusForbidden)return
}}}func root_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token")if token != "" { id, _ := jwt_decode(token)acc := get_account(id)tpl, err := template.New("").Parse("Logged in as " + acc.id)if err != nil { }tpl.Execute(w, &acc)} else {return
}}func main() { admin := Account{ admin_id, admin_pw, true, secret_key}acc = append(acc, admin)http.HandleFunc("/", root_handler)http.HandleFunc("/auth", auth_handler)http.HandleFunc("/flag", flag_handler)http.HandleFunc("/regist", regist_handler)log.Fatal(http.ListenAndServe("0.0.0.0:11000", nil))}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.43.44.45.46.47.48.49.50.51.52.53.54.55.56.57.58.59.60.61.62.63.64.65.66.67.68.69.70.71.72.73.74.75.76.77.78.79.80.81.82.83.84.85.86.87.88.89.90.91.92.93.94.95.96.97.98.99.100.101.102.103.104.105.106.107.108.109.110.111.112.113.114.115.116.117.118.119.120.121.122.123.124.125.126.127.128.129.130.131.132.133.134.135.136.137.138.139.140.141.142.143.144.145.146.147.148.149.150.151.152.153.154.155.156.157.158.159.160.161.162.163.164.165.166.167.168.169.170.171.172.我们先对几个路由和其对应的函数进行分析 。
struct结构 复制type Account struct {id string
pw string
is_admin boolsecret_key string
}1.2.3.4.5.6. 注册功能 复制func regist_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id")upw := r.FormValue("pw")if uid == "" || upw == "" {return
}if get_account(uid).id != "" { w.WriteHeader(http.StatusForbidden)return
}if len(acc) > 4 { clear_account()}new_acc := Account{ uid, upw, false, secret_key} //创建新用户
acc = append(acc, new_acc)p := Resp{ true, ""}res, err := json.Marshal(p)if err != nil { }w.Write(res)return
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25. 登录功能 复制func auth_handler(w http.ResponseWriter, r *http.Request) { uid := r.FormValue("id")upw := r.FormValue("pw")if uid == "" || upw == "" {return
}if len(acc) > 1024 { clear_account()}user_acc := get_account(uid)if user_acc.id != "" && user_acc.pw == upw { //检验id和pw
token, err := jwt_encode(user_acc.id, user_acc.is_admin)if err != nil {return
}p := TokenResp{ true, token} //返回token
res, err := json.Marshal(p)if err != nil { }w.Write(res)return
}w.WriteHeader(http.StatusForbidden)return
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25. 认证功能 复制func root_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token")if token != "" { //根据token解出id ,根据uid取出对应account
id, _ := jwt_decode(token)acc := get_account(id)tpl, err := template.New("").Parse("Logged in as " + acc.id)if err != nil { }tpl.Execute(w, &acc)} else {return
}}1.2.3.4.5.6.7.8.9.10.11.12.13.14. 得到account 复制func get_account(uid string) Account { for i := range acc { if acc[i].id == uid { return acc[i]}}return Account{ }}1.2.3.4.5.6.7.8. flag路由 复制func flag_handler(w http.ResponseWriter, r *http.Request) { token := r.Header.Get("X-Token")if token != "" { id, is_admin := jwt_decode(token)if is_admin == true { //将is_admin修改为true即可得到flag
p := Resp{ true, "Hi " + id + ", flag is " + flag}res, err := json.Marshal(p)if err != nil { }w.Write(res)return
} else { w.WriteHeader(http.StatusForbidden)return
}}}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.所以思路就清晰了 ,我们需要得到secret_key,然后继续jwt伪造得到flag。
而由于root_handler函数中得到的acc是数组中的地址,即会在全局变量acc函数中查找我们的用户,这时传入``.`secret_key`会返回空 ,所以我们用``.``来得到结构体内所有内容 。
复制/regist?id={ { .}}&pw=1231.


得到secret_key,进行jwt伪造,把 is_admin修改为true,key填上secret_key得到
复制eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6Int7Ln19IiwiaXNfYWRtaW4iOnRydWV9.3OXFk-f_S2XqPdzHnl0esmJQXuTSXuA1IbpaGOMyvWo1.
带上token访问/flag

[WeCTF2022]request-bin
洁白一片,使用``.``进行检测

这道题目采用的框架是iris,用户可以对日志的格式参数进行控制 ,而参数又会被当成模板渲染,所以我们就可以利用该点进行ssti。
我们需要的是进行文件的读取,所以我们需要看看iris的accesslog库的模板注入如何利用 。
在Accesslog的结构体中可以发现
复制type Log struct { //The AccessLog instance this Log was created of.
Logger *AccessLog `json:"-" yaml:"-" toml:"-"`
// The time the log iscreated.
Now time.Time `json:"-" yaml:"-" toml:"-"`
// TimeFormat selected to print the Time as string,// useful onTemplate Formatter.
TimeFormat string `json:"-" yaml:"-" toml:"-"`
// Timestamp the Nows unix timestamp (milliseconds).Timestamp int64 `json:"timestamp" csv:"timestamp"`// Request-Response latency.Latency time.Duration `json:"latency" csv:"latency"`// The response status code.Code int `json:"code" csv:"code"`// Init requests Method andPath.
Method string `json:"method" csv:"method"`
Path string `json:"path" csv:"path"`
//The Remote Address.
IP string `json:"ip,omitempty" csv:"ip,omitempty"`
//Sorted URL Query arguments.
Query []memstore.StringEntry `json:"query,omitempty" csv:"query,omitempty"`
//Dynamic path parameters.
PathParams memstore.Store `json:"params,omitempty" csv:"params,omitempty"`
//Fields any data information useful to represent this Log.
Fields memstore.Store `json:"fields,omitempty" csv:"fields,omitempty"`
// The Request andResponse raw bodies.
// If they are escaped (e.g. JSON),// A third-party software can read it through:// data, _ := strconv.Unquote(log.Request)// err := json.Unmarshal([]byte(data), &customStruct)Request string `json:"request,omitempty" csv:"request,omitempty"`
Response string `json:"response,omitempty" csv:"response,omitempty"`
// The actual number of bytes received and sent on the network (headers + body or body only).
BytesReceived int `json:"bytes_received,omitempty" csv:"bytes_received,omitempty"`
BytesSent int `json:"bytes_sent,omitempty" csv:"bytes_sent,omitempty"`
// A copy of the Requests Context when Async is true (safe to use concurrently),// otherwise its the current Context (not safe for concurrent access).
Ctx *context.Context `json:"-" yaml:"-" toml:"-"`
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23.24.25.26.27.28.29.30.31.32.33.34.35.36.37.38.39.40.41.42.这里我们经过审查,会发现context里面存在SendFile进行文件强制下载。

所以我们可以构造payload如下 :
复制{ { .Ctx.SendFile "/flag" "1.txt"}}1.
后言
golang的template跟很多模板引擎的语法差不多 ,比如双花括号指定可解析的对象,假如我们传入的参数是可解析的 ,就有可能造成泄露 ,其本质就是合并替换,而常用的检测payload可以用占位符.,对于该漏洞的防御也是多注意对传入参数的控制 。
Tags:
转载:欢迎各位朋友分享到网络,但转载请说明文章出处“信息技术视野”。http://www.bziz.cn/html/123d799869.html
相关文章
高级分析可以帮助快速检测内部威胁
IT资讯虽然外部网络威胁占据头条新闻,但来自组织内部的内部威胁的增加越来越令人担忧。2023年,内部人员造成的数据泄露平均成本达到490万美元,比全球平均数据泄露成本445万美元高出9.6%。为了有效应对这种 ...
【IT资讯】
阅读更多揭秘苹果耳机6s音质的绝佳表现(聆听细节世界,感受耳边的音乐奇迹)
IT资讯在音乐领域,音质一直是用户追求的关键因素之一。作为苹果公司旗下的经典产品,苹果耳机6s在音质方面一直备受推崇。本文将深入探究苹果耳机6s的音质特点,并带您领略其带来的音乐奇迹。1.独具特色的高频表现苹 ...
【IT资讯】
阅读更多华为推出原生科学智能基础软硬件平台,倡议成立科学智能创新联合体
IT资讯[中国,深圳,2022年11月9日]华为全联接大会2022在深圳线下和线上同步举行,在《共建产业根基,共筑数智生态》分享环节,华为推出原生科学智能基础软硬件平台,并倡议成立科学智能创新联合体,携手产学 ...
【IT资讯】
阅读更多
热门文章
最新文章
友情链接
- 第二届“长城杯”信息安全铁人三项赛(防护赛)总决赛圆满收官
- 微软又全球宕机11小时,多项核心服务无法使用
- TrafficPeak可观察性解决方案:洞悉云中万物
- Apache Roller 曝出高危漏洞(CVSS 10.0):密码修改后会话仍持续有效
- 图解+案例,理解和实战 OAuth2 认证授权
- Craft CMS 零日漏洞 CVE-2025-32432 现可通过公开 Metasploit 模块利用
- 大规模DOS攻击利用280万个IP攻击VPN和防火墙登录
- 网络安全里的人工智能模型:从误用到滥用
- 黑客利用YouTube 平台传播复杂的恶意软件
- 网络安全问题,好多同学不知道啊...... 网站建设源码库亿华云云服务器企业服务器b2b信息平台香港物理机