説明
ユーザー入力を適切にエスケープせずにHTMLへ出力すると、<script> タグや onclick などのイベントハンドラーを挿入されるおそれがあります。ブラウザーがその入力をページのコードとして解釈すると、被害者のブラウザーでJavaScriptが実行され、セッションの悪用、フィッシング、キー入力の記録などにつながります。
想定される影響
- スクリプトから読めるCookieやトークンを盗まれたり、ユーザーの権限で要求を送られたりする可能性があります。
- 悪意のあるJavaScriptがDOMの変更、ブラウザーの転送、キー入力の記録などを行うおそれがあります。
- 偽のログイン・決済フォームによって、機密情報を入力させられる可能性があります。
- 不正なポップアップや転送が信頼を損ない、金銭的・法的な損失につながる場合があります。
対処方法
- HTML応答では
html/templateと信頼済みのテンプレートを使い、ユーザー入力を通常のデータとして渡してください。 http.ResponseWriter.Write()自体が危険なのではありません。ユーザー入力を直接連結したHTMLを提供しないでください。- HTML、URL、JavaScriptなどの出力文脈に応じて処理し、ユーザー入力からテンプレート自体を作らないでください。
- 外部入力を
template.HTML、template.URL、template.JSなど、信頼を表す型へ変換しないでください。 - URLや名前の形式を検証しても、出力文脈に応じた処理を省略しないでください。
例
HTMLとして提供する応答を比較します。html/template のURL文脈処理は危険なスキームや属性の挿入を制限しますが、外部リンクのドメイン許可方針までは定めません。
変更前
go
package main
import (
"fmt"
"net/http"
)
// 変更前: ユーザー入力をResponseWriterへ直接書き込む
func errorPage(w http.ResponseWriter, r *http.Request) {
// ユーザーが操作できるクエリパラメーター
url := r.URL.Query().Get("url")
// ユーザー入力を含むHTML
const template = `
<html>
<body>
<h1>error; page not found. <a href="%s">go back</a></h1>
</body>
</html>`
// 文字列を組み立てて直接書き込む
// urlには "javascript:alert(1)" や "\" onclick=\"alert(1)" などが入力され得る
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte(fmt.Sprintf(template, url)))
}
func main() {
http.HandleFunc("/error", errorPage)
http.ListenAndServe(":8080", nil)
}
変更後
go
package main
import (
"html/template"
"log"
"net/http"
)
// 変更後: html/templateによる自動エスケープ
// テンプレートの{{.URL}}位置へデータを渡し、
// html/templateが文脈に応じて自動エスケープする
var errorTmpl = template.Must(template.New("error").Parse(`
<html>
<body>
<h1>error; page not found. <a href="{{.URL}}">go back</a></h1>
</body>
</html>`))
func errorPageSafe(w http.ResponseWriter, r *http.Request) {
url := r.URL.Query().Get("url")
// URLが空なら既定値を設定
if url == "" {
url = "/"
}
w.WriteHeader(http.StatusBadRequest)
// データ構造をテンプレートに渡す
data := struct {
URL string
}{URL: url}
if err := errorTmpl.Execute(w, data); err != nil {
log.Println("template execute error:", err)
}
}
func main() {
http.HandleFunc("/error", errorPageSafe)
http.ListenAndServe(":8080", nil)
}
解説:
- 変更前:
fmt.Sprintf()でHTMLへurlパラメーターを直接挿入し、w.Write()でブラウザーへ送ります。" onclick="alert(1)などを入力されると<a>属性の境界を抜け、JavaScriptを挿入されるおそれがあります。 - 変更後: 信頼済みテンプレートの
href位置へ通常の文字列を渡します。html/templateがURL文脈を処理し、危険なスキームや属性の境界からの脱出を制限します。ユーザー入力をtemplate.URLやtemplate.HTMLへ変換して処理を迂回しないでください。