[インデックス 14907] ファイルの概要
このコミットは、Go言語の標準ライブラリである html/template
パッケージから、未ドキュメントかつ不完全に実装されていた noescape
機能のサポートを削除するものです。この変更は、Go 1.1 リリースの一部として行われ、テンプレートエンジンのセキュリティと堅牢性を向上させることを目的としています。特に、クロスサイトスクリプティング (XSS) 攻撃のリスクを低減するために、コンテキストに応じた自動エスケープの原則を厳格に適用するための措置です。
コミット
commit c02294344943db0e93d24d37526961b3fe851a66
Author: Andrew Gerrand <adg@golang.org>
Date: Fri Jan 18 10:30:12 2013 +1100
html/template: remove noescape support
This was never documented or properly implemented.
Fixes #3528.
R=mikesamuel, rsc
CC=golang-dev
https://golang.org/cl/7142048
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c02294344943db0e93d24d37526961b3fe851a66
元コミット内容
html/template: remove noescape support
This was never documented or properly implemented.
Fixes #3528.
R=mikesamuel, rsc
CC=golang-dev
https://golang.org/cl/7142048
変更の背景
この変更の背景には、Go言語の html/template
パッケージが提供するセキュリティ機能、特にコンテキストに応じた自動エスケープの堅牢性を確保するという強い意図があります。コミットメッセージに「This was never documented or properly implemented. (これは一度もドキュメント化されておらず、適切に実装されていなかった)」とあるように、noescape
機能は設計上の問題や実装の不完全さを抱えていました。
noescape
のような機能は、開発者が意図的にエスケープ処理をスキップすることを可能にするため、誤用されるとクロスサイトスクリプティング (XSS) 攻撃の脆弱性を生み出す可能性があります。html/template
パッケージの主要な目的は、開発者が安全なウェブアプリケーションを構築できるよう、デフォルトでXSS攻撃から保護することです。不完全な noescape
機能が存在することは、このセキュリティ保証を損なうリスクがありました。
また、Fixes #3528
とあるように、このコミットはGoのIssue 3528に対応しています。このIssueは、html/template
の noescape
機能が期待通りに動作しない、またはその存在自体が混乱を招くという報告に関連していると考えられます。未ドキュメントで不完全な機能を削除することで、コードベースの複雑性を減らし、将来的な誤用やセキュリティホールを防ぐことが目的です。
前提知識の解説
Go言語の html/template
パッケージ
html/template
パッケージは、Go言語でHTML出力を生成するためのテンプレートエンジンです。このパッケージの最大の特徴は、コンテキストに応じた自動エスケープ (Contextual Auto-escaping) 機能です。これは、テンプレートに挿入されるデータが、そのデータがHTMLドキュメント内のどの「コンテキスト」(例:HTML要素のテキストコンテンツ、属性値、JavaScriptコード、CSSスタイルなど)に表示されるかに応じて、自動的に適切なエスケープ処理が施されるというものです。
例えば、ユーザーが入力した文字列がHTML要素のテキストとして表示される場合、<
は <
に、>
は >
にエスケープされます。しかし、その文字列がJavaScriptの文字列リテラルとして表示される場合、引用符やバックスラッシュなどがエスケープされます。この自動エスケープにより、開発者が手動でエスケープ処理を記述する手間を省き、同時にXSS攻撃のリスクを大幅に低減します。
クロスサイトスクリプティング (XSS) 攻撃
XSSは、ウェブアプリケーションのセキュリティ脆弱性の一種で、攻撃者が悪意のあるスクリプト(通常はJavaScript)をウェブページに注入し、そのスクリプトが他のユーザーのブラウザで実行されることを可能にします。これにより、攻撃者はセッションクッキーの盗難、ウェブサイトの改ざん、ユーザーの個人情報の窃取など、様々な悪意のある行為を行うことができます。
XSS攻撃は、ユーザー入力が適切にエスケープされずにHTML出力に直接埋め込まれる場合に発生します。例えば、ユーザー名に <script>alert('XSS')</script>
のような文字列が含まれており、それがそのままウェブページに表示されると、そのスクリプトが実行されてしまいます。
noescape
機能の危険性
noescape
のような機能は、テンプレートエンジンが提供する自動エスケープを無効にするためのものです。これは、開発者が「このデータはすでに安全である」と判断した場合に、エスケープ処理のオーバーヘッドを避けるためや、意図的にHTMLタグなどを出力したい場合に利用されることがあります。
しかし、noescape
の使用は非常に危険です。開発者がデータの安全性を誤って判断したり、外部からの入力が予期せず含まれてしまうと、XSS脆弱性を直接導入することになります。html/template
のようなセキュリティを重視したテンプレートエンジンでは、このような「エスケープを無効にする」機能は、極めて慎重に扱われるべきであり、通常は提供されないか、非常に限定的な状況でのみ許可されます。このコミットで noescape
が削除されたのは、その不完全な実装と、セキュリティ上のリスクが、その潜在的な利点を上回ると判断されたためです。
技術的詳細
このコミットは、html/template
パッケージの内部におけるエスケープ処理のロジックから、noescape
フィルターの認識と処理を完全に削除しています。
-
src/pkg/html/template/escape.go
の変更: このファイルは、html/template
パッケージの自動エスケープロジックの核心部分です。ensurePipelineContains
関数は、テンプレートのパイプライン({{. | func1 | func2}}
のような一連の処理)を分析し、特定のコンテキストで必要なエスケープ関数がパイプラインに含まれていることを確認します。 変更前は、この関数内でnoescape
という識別子を持つコマンドが存在するかどうかをチェックし、もし存在すれば、それ以上のエスケープ処理をスキップするロジックがありました。 変更後、このnoescape
のチェックが完全に削除されました。これにより、noescape
という名前の関数がテンプレート内で使用されても、特別な意味を持たなくなり、通常のデータとして扱われるか、未定義の関数としてエラーになるかのいずれかになります。重要なのは、エスケープ処理をスキップするメカニズムがなくなったことです。 -
src/pkg/html/template/escape_test.go
の変更: このファイルは、html/template
パッケージのエスケープ処理に関する単体テストを含んでいます。変更前は、noescape
機能の動作を検証するためのテストケースが存在していました。具体的には、"auditable exemption from escaping"
というテストケースがあり、{{range .A}}{{. | noescape}}{{end}}
のようなテンプレートが、エスケープされずに<a><b>
という出力になることを期待していました。 また、テストヘルパーとしてnoescape
という名前の関数をFuncs
に登録する部分も存在しました。 これらのテストケースと関連するヘルパーコードは、noescape
機能の削除に伴い、すべて削除されました。これは、もはやnoescape
がサポートされないため、その動作をテストする必要がなくなったことを意味します。 -
doc/go1.1.html
の変更: このファイルは、Go 1.1 リリースノートのドキュメントです。このコミットでは、html/template
セクションに新しい段落が追加されました。 「Templates using the undocumented and only partially implemented "noescape" feature will break: that feature was removed. (未ドキュメントで部分的にしか実装されていなかった"noescape"機能を使用しているテンプレートは動作しなくなります。その機能は削除されました。)」 このドキュメントの追加は、noescape
機能に依存していた既存のコードがGo 1.1で動作しなくなることを開発者に明確に通知するためのものです。これは、後方互換性のない変更であることを示しており、その影響を最小限に抑えるための重要な情報提供です。
これらの変更は、html/template
パッケージが提供するセキュリティ保証を強化し、開発者が誤ってXSS脆弱性を導入するリスクを低減するためのものです。
コアとなるコードの変更箇所
src/pkg/html/template/escape.go
--- a/src/pkg/html/template/escape.go
+++ b/src/pkg/html/template/escape.go
@@ -220,10 +220,7 @@ func ensurePipelineContains(p *parse.PipeNode, s []string) {
idents := p.Cmds
for i := n - 1; i >= 0; i-- {
if cmd := p.Cmds[i]; len(cmd.Args) != 0 {
-\t\t\tif id, ok := cmd.Args[0].(*parse.IdentifierNode); ok {\n-\t\t\t\tif id.Ident == \"noescape\" {\n-\t\t\t\t\treturn\n-\t\t\t\t}\n+\t\t\tif _, ok := cmd.Args[0].(*parse.IdentifierNode); ok {\n \t\t\t\tcontinue
\t\t\t}
\t\t}
src/pkg/html/template/escape_test.go
--- a/src/pkg/html/template/escape_test.go
+++ b/src/pkg/html/template/escape_test.go
@@ -550,11 +550,6 @@ func TestEscape(t *testing.T) {\n \t\t\t\"<textarea>{{range .A}}{{.}}{{end}}</textarea>\",\n \t\t\t\"<textarea><a><b></textarea>\",\n \t\t},\n-\t\t{\n-\t\t\t\"auditable exemption from escaping\",\n-\t\t\t\"{{range .A}}{{. | noescape}}{{end}}\",\n-\t\t\t\"<a><b>\",\n-\t\t},\n \t\t{\n \t\t\t\"No tag injection\",\n \t\t\t`{{\"10$\"}}<{{\"script src,evil.org/pwnd.js\"}}...`,\n@@ -659,12 +654,6 @@ func TestEscape(t *testing.T) {\n \n \tfor _, test := range tests {\n \t\ttmpl := New(test.name)\n-\t\t// TODO: Move noescape into template/func.go\n-\t\ttmpl.Funcs(FuncMap{\n-\t\t\t\"noescape\": func(a ...interface{}) string {\n-\t\t\t\treturn fmt.Sprint(a...)\n-\t\t\t},\n-\t\t})\n \t\ttmpl = Must(tmpl.Parse(test.input))\n \t\tb := new(bytes.Buffer)\n \t\tif err := tmpl.Execute(b, data); err != nil {\n```
## コアとなるコードの解説
### `src/pkg/html/template/escape.go` の変更点
`escape.go` の変更は、`ensurePipelineContains` 関数内のロジックにあります。この関数は、テンプレートのパイプライン(`{{. | func1 | func2}}` のような形式)を走査し、特定のコンテキストで必要なエスケープ関数が適切に適用されているかを確認します。
変更前は、以下のコードがありました。
```go
if id, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
if id.Ident == "noescape" {
return
}
これは、コマンドの最初の引数が識別子(関数名など)であり、その識別子が "noescape"
であった場合に、現在の ensurePipelineContains
関数の実行を即座に終了(return
)するという意味です。つまり、noescape
フィルターがパイプラインに存在する場合、それ以上のエスケープ処理のチェックや追加が行われないようにしていました。これは、noescape
が「この部分はエスケープ不要」という指示として機能していたことを示唆しています。
変更後、この if id.Ident == "noescape"
の条件分岐が削除されました。
if _, ok := cmd.Args[0].(*parse.IdentifierNode); ok {
continue
}
これにより、noescape
という識別子があっても特別な処理は行われず、他の識別子と同様に扱われます。結果として、noescape
はもはやエスケープ処理をスキップする特別な意味を持たなくなりました。この変更により、html/template
は常にコンテキストに応じたエスケープ処理を適用しようとします。
src/pkg/html/template/escape_test.go
の変更点
escape_test.go
の変更は、noescape
機能に関連するテストケースと、そのテストをサポートするためのヘルパー関数の削除です。
-
テストケースの削除:
TestEscape
関数内のテストデータスライスから、"auditable exemption from escaping"
という名前のテストケースが削除されました。このテストケースは、{{range .A}}{{. | noescape}}{{end}}
というテンプレートがエスケープされずに<a><b>
と出力されることを検証していました。noescape
機能が削除されたため、このテストケースは不要となり、削除されました。 -
noescape
ヘルパー関数の削除:TestEscape
関数のループ内で、tmpl.Funcs
を使用してnoescape
という名前のカスタム関数をテンプレートに登録する部分が削除されました。このカスタム関数は、引数をそのまま文字列として返すだけのシンプルなもので、テスト目的でnoescape
フィルターの存在をシミュレートするために使用されていました。機能が削除されたため、このヘルパー関数も不要となり、削除されました。
これらのテストコードの削除は、noescape
機能が完全に廃止され、その動作を検証する必要がなくなったことを明確に示しています。
関連リンク
- Go Issue 3528: https://github.com/golang/go/issues/3528 (このコミットが修正したIssue)
- Go
html/template
パッケージのドキュメント: https://pkg.go.dev/html/template - Go 1.1 リリースノート (変更が記載されている箇所): https://go.dev/doc/go1.1 (このコミットで追加されたドキュメント部分も確認できます)
参考にした情報源リンク
- Go言語の公式ドキュメント (
html/template
パッケージ、リリースノート) - GitHubのGoリポジトリのIssueトラッカー
- 一般的なウェブセキュリティ(XSS攻撃、エスケープ処理)に関する知識