Go语言下的模板注入
字数 936 2025-08-05 11:39:48
Go语言模板注入漏洞分析与防御
Go语言基础回顾
基本语法结构
package main // 定义包名,main表示可独立执行程序
import "fmt" // 导入fmt包,实现格式化IO
func main() { // 程序入口函数
fmt.Println("Hello, World!") // 输出到控制台并换行
}
格式化字符串
Go语言提供两种主要格式化方法:
fmt.Sprintf- 生成格式化字符串并返回fmt.Printf- 生成格式化字符串并写入标准输出
var stockcode = 123
var enddate = "2020-12-31"
var url = "Code=%d&endDate=%s"
// Sprintf示例
target_url := fmt.Sprintf(url, stockcode, enddate)
// Printf示例
fmt.Printf(url, stockcode, enddate)
变量声明方式
-
指定类型声明:
var v_name v_type v_name = value -
类型推断声明:
var v_name = value -
简短声明:
v_name := value // 只能用于新变量声明
常量定义
const identifier [type] = value
const b string = "abc" // 显式类型
const b = "abc" // 隐式类型
const c_name1, c_name2 = value1, value2 // 多常量声明
循环语句
Go语言for循环有三种形式:
-
传统C风格:
for init; condition; post { } -
while风格:
for condition { } -
无限循环:
for { } -
range迭代:
for key, value := range oldMap { newMap[key] = value }
Go模板引擎
Go提供两个模板包:
html/template- 用于安全HTML输出,自动防止XSStext/template- 纯文本模板,无自动转义
关键区别
html/template会自动转义HTML、CSS、JS和URItext/template直接输出原始内容,不进行转义
// 使用text/template时
t, err := template.New("foo").Parse(`{{define "T"}}Hello, {{.}}!{{end}}`)
t.ExecuteTemplate(out, "T", "<script>alert('xss')</script>")
// 输出: Hello, <script>alert('xss')</script>!
// 使用html/template时
// 输出: Hello, <script>alert('xss')</script>!
模板注入漏洞分析
漏洞示例代码
package main
import (
"fmt"
"net/http"
"strings"
"text/template" // 关键漏洞点:使用了text/template
)
type User struct {
Id int
Name string
Passwd string
}
func StringTplExam(w http.ResponseWriter, r *http.Request) {
user := &User{1, "admin", "123456"}
r.ParseForm()
arg := strings.Join(r.PostForm["name"], "") // 用户输入点
// 直接拼接用户输入到模板
tpl1 := fmt.Sprintf(`<h1>Hi, ` + arg + `</h1> Your name is ` + arg + `!`)
html, err := template.New("login").Parse(tpl1) // 解析模板
html = template.Must(html, err)
html.Execute(w, user) // 执行模板渲染
}
func main() {
server := http.Server{
Addr: "127.0.0.1:8080",
}
http.HandleFunc("/login", StringTplExam)
server.ListenAndServe()
}
主要漏洞点
-
敏感信息泄露:
- 通过模板访问User结构体字段
- 可泄露Id、Name、Passwd等敏感信息
-
远程代码执行(RCE):
- 通过引入危险函数实现命令执行
func (u User) Secret(test string) string { out, _ := exec.Command(test).CombinedOutput() return string(out) } -
文件读取:
- 通过危险方法实现任意文件读取
func (u *User) FileRead(File string) string { data, err := ioutil.ReadFile(File) if err != nil { fmt.Print("File read error") } return string(data) } -
XSS攻击:
- 使用text/template时不转义用户输入
- 可直接注入恶意脚本
漏洞利用方法
1. 敏感信息泄露利用
通过模板语法访问结构体字段:
{{.Id}} // 获取用户ID
{{.Name}} // 获取用户名
{{.Passwd}} // 获取密码
2. 远程代码执行
如果定义了危险方法,可通过模板调用:
{{.Secret "whoami"}} // 执行系统命令
3. 文件读取利用
{{.FileRead "/etc/passwd"}} // 读取系统文件
4. XSS注入
直接提交恶意脚本:
<script>alert('XSS')</script>
防御措施
防御方法1:安全模板变量
func StringTpl2Exam(w http.ResponseWriter, r *http.Request) {
user := &User{1, "tyskill", "tyskill"}
r.ParseForm()
arg := strings.Join(r.PostForm["name"], "")
// 使用安全模板语法
tpl := `<h1>Hi, {{ .arg }}</h1><br>Your name is {{ .Name }}`
// 使用map存储模板数据
data := map[string]string{
"arg": arg,
"Name": user.Name,
}
html := template.Must(template.New("login").Parse(tpl))
html.Execute(w, data)
}
防御方法2:使用html/template
import "html/template" // 使用安全模板包
// 自动转义HTML特殊字符
防御方法3:手动转义
-
使用内置转义函数:
{{html .userInput}} // HTML转义 {{js .userInput}} // JS转义 -
使用转义函数:
template.HTMLEscapeString(userInput)
最佳实践
- 始终优先使用html/template处理用户输入
- 避免直接拼接用户输入到模板字符串
- 使用安全模板语法({{.var}}形式)
- 对动态内容进行转义处理
- 限制模板可访问的方法,避免危险函数暴露
- 最小化模板权限,只暴露必要的数据
总结
Go语言模板注入漏洞主要源于:
- 使用了不安全的text/template包
- 直接拼接用户输入到模板
- 暴露了危险方法给模板引擎
防御关键在于:
- 选择合适的模板引擎
- 安全处理用户输入
- 严格控制模板可访问的内容
通过遵循安全编码实践,可以有效预防Go语言中的模板注入漏洞。