説明
ユーザー入力を JavaScript コードに直接連結すると、攻撃者がページ内でスクリプトを実行できるおそれがあります。template.JS は値を信頼済みの JavaScript として扱う型であり、入力の検証やエスケープは行いません。
想定される影響
- ページからアクセスできる機密情報の読み取りや、ユーザーの権限によるリクエスト送信
- ページの改ざんや偽の入力フォームによる情報収集
対処方法
- ユーザー入力を
template.JSに変換したり、JavaScript コードに直接連結したりしないでください。 - 通常の文字列を
html/templateに渡し、出力先の文脈に応じたエスケープを適用してください。 - 以下の例のようにデータ属性で値を渡し、DOM 要素を作成してから JavaScript で読み取ってください。
例
変更前
go
package main
import (
"html/template"
"net/http"
)
// 脆弱なパターンを示す例です。
func UnsafeJS(w http.ResponseWriter, r *http.Request) {
// id クエリパラメータからのユーザー入力
userID := r.URL.Query().Get("id")
// 脆弱: ユーザー入力を HTML/JS に直接連結
// template.JS に変換しても入力は検証もエスケープもされない
js := "var msg = '<h1>" + userID + "</h1>'; document.write(msg);"
// ruleid: unescaped-data-in-js
safeLike := template.JS(js) // この値は安全ではない
w.Header().Set("Content-Type", "text/html; charset=utf-8")
_, _ = w.Write([]byte("<script>" + string(safeLike) + "</script>"))
}
変更後
go
package main
import (
"html/template"
"net/http"
)
// {{.UserID}} に挿入するデータはテンプレートがエスケープする
var pageTmpl = template.Must(template.New("page").Parse(`
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><title>Profile</title></head>
<body>
<h1>User: {{.UserID}}</h1>
<div id="user" data-userid="{{.UserID}}"></div>
<script>
// データは data-* 属性や適切な JSON エンコードで渡す
const userId = document.querySelector('#user').dataset.userid;
console.log('User:', userId);
</script>
</body>
</html>
`))
type PageData struct {
UserID string
}
func SafeJS(w http.ResponseWriter, r *http.Request) {
userID := r.URL.Query().Get("id")
// 必要に応じてサーバーで長さや許可する文字を検証する
// if !validUserID(userID) { ... }
data := PageData{UserID: userID}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
// html/template が出力先の文脈に応じてエスケープする
_ = pageTmpl.Execute(w, data)
}
説明: 変更前は、ユーザー入力から組み立てたスクリプトをレスポンスに直接書き込んでいます。変更後は UserID をテンプレートのテキストと属性値として出力し、JavaScript が作成済みの要素から読み取ります。読み取った値を再び HTML やコードとして解釈しないでください。