クロスサイトスクリプティング(XSS)

クロスサイトスクリプティング

説明

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 色であることを保証するものではありません。

参考資料