説明
template.URL は値を信頼済みの URL として扱う型です。入力を検証するものではなく、html/template で使うと、通常の文字列に適用される URL スキームのフィルタを回避する可能性があります。ユーザー入力を含むリンクは通常の文字列としてテンプレートに渡し、遷移先も検証してください。
想定される影響
- 危険な URL やエスケープされていない HTML 属性による、ページ内でのスクリプト実行
- ページからアクセスできる情報の読み取りや、ユーザーの権限によるリクエスト送信
対処方法
- ユーザー入力を
template.URLに変換しないでください。 - HTML を直接連結せず、
html/templateの文脈に応じたエスケープを使用してください。 - 許可する遷移先を明示的に制限し、クエリの値は
url.QueryEscapeでエンコードしてください。クエリのエンコードは、HTML のエスケープや遷移先の検証の代わりにはなりません。
例
変更前
go
package main
import (
"html/template"
"net/http"
)
// 不適切: ユーザー入力をそのまま template.URL() に渡す
func UserProfileHandler(w http.ResponseWriter, r *http.Request) {
// ユーザー入力の例: /profile?redirect=https://example.com
redirect := r.URL.Query().Get("redirect")
// 単一引用符を含む入力で href 属性を抜け出せる
// 脆弱: ユーザー入力を含む文字列を template.URL() に渡す
url := "/go?next=" + redirect
// template.URL に変換しても直接組み立てた HTML はエスケープされない
safeURL := template.URL(url)
// 文字列を連結して HTML 出力を直接組み立てる
html := "<a href='" + string(safeURL) + "'>계속하기</a>"
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html))
}
変更後
go
package main
import (
"html/template"
"net/http"
"net/url"
)
var tmpl = template.Must(template.New("link").Parse(`
<!DOCTYPE html>
<html>
<body>
<a href="{{.SafeNext}}">계속하기</a>
</body>
</html>
`))
// 入力を検証し、html/template でエスケープする
func UserProfileHandler(w http.ResponseWriter, r *http.Request) {
rawNext := r.URL.Query().Get("redirect")
// 1. 明示的に許可した内部の遷移先だけを使用する
if rawNext != "/home" && rawNext != "/profile" {
rawNext = "/home"
}
// 2. 遷移先を next クエリパラメータの値としてエンコードする
encodedNext := url.QueryEscape(rawNext)
// 3. データをテンプレートに渡して自動エスケープを適用する
data := struct {
SafeNext string
}{
SafeNext: "/go?next=" + encodedNext,
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
if err := tmpl.Execute(w, data); err != nil {
http.Error(w, "internal error", http.StatusInternalServerError)
return
}
}
説明: 変更前は、入力に含まれる単一引用符で href 属性を抜け出し、HTML を挿入できます。この例では、/go?next= の後に javascript: を付けるだけで実行可能な URL スキームになるわけではありません。変更後は /home と /profile だけを許可し、遷移先をクエリの値としてエンコードしたリンクをテンプレートに渡します。実際にリダイレクトを行う /go ハンドラーでも遷移先の検証が必要です。