[インデックス 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攻撃、エスケープ処理)に関する知識