説明
template.HTMLAttr は文字列を信頼済みの HTML 属性として扱い、エスケープしません。ユーザー入力から属性全体を組み立ててこの型に変換すると、攻撃者が引用符で属性値を抜け出し、イベントハンドラーなどを挿入するおそれがあります。
想定される影響
- 被害者のページ内での JavaScript 実行
- ページからアクセスできる情報の漏えい、画面の改ざん、ユーザーの権限によるリクエスト送信
対処方法
- 属性名と構造はテンプレート内で固定し、値だけを通常の文字列として渡してください。
- ユーザー入力を
template.HTMLAttrに変換せず、html/templateの文脈に応じたエスケープを使用してください。 - 値の用途に合わせて、許可する文字と長さを制限してください。
例
変更前
go
package main
import (
"html/template"
"net/http"
)
func unsafeHandler(w http.ResponseWriter, r *http.Request) {
// クエリパラメータからユーザー入力を取得
color := r.URL.Query().Get("color") // 想定する入力の例: "red"
// 不適切: ユーザー入力を HTML 属性に直接連結
// 攻撃例: color="red" onclick="alert(1)"
attr := "style=\"color:" + color + "\""
// template.HTMLAttr は値をエスケープしない
safeAttr := template.HTMLAttr(attr)
// attr をそのままテンプレートに渡す
t := template.Must(template.New("page").Parse(`
<html><body>
<p {{.}}>Hello</p>
</body></html>`))
// 挿入された属性によってブラウザーでコードが実行される可能性がある
_ = t.Execute(w, safeAttr)
}
変更後
go
package main
import (
"html/template"
"net/http"
"regexp"
)
var pageTmpl = template.Must(template.New("page").Parse(`
<html><body>
<p style="color:{{.Color}}">Hello</p>
</body></html>`))
// 文字の種類と長さを制限する。CSS 色名の妥当性までは検証しない
var colorRe = regexp.MustCompile(`^[a-zA-Z0-9_-]{1,20}$`)
func safeHandler(w http.ResponseWriter, r *http.Request) {
color := r.URL.Query().Get("color")
if !colorRe.MatchString(color) {
// 検証に失敗した場合は既定値を使用
color = "black"
}
// html/template に通常の文字列を渡す
// テンプレートが出力先の文脈に応じてエスケープする
data := struct {
Color string
}{Color: color}
if err := pageTmpl.Execute(w, data); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
説明: 変更前は、red" onclick="alert(1) のような入力によって新しいイベント属性を作成できます。変更後は Color を通常の文字列として渡し、CSS 値に適したフィルタとエスケープを適用します。正規表現は文字の種類と長さを制限するもので、許可された値がすべて有効な CSS 色であることを保証するものではありません。