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

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

説明

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 ハンドラーでも遷移先の検証が必要です。

参考資料