[インデックス 18051] ファイルの概要
このコミットは、Go言語の標準ライブラリである encoding/json パッケージ内の HTMLEscape 関数に対する新しいテストケースを追加するものです。HTMLEscape 関数は、JSONエンコーディングの際に特定のHTML特殊文字をエスケープし、生成されたJSONがHTMLドキュメント内に安全に埋め込まれるようにするために使用されます。このテストの追加により、HTMLEscape 関数の堅牢性と正確性が向上します。
コミット
commit aa20d2629284ee73637598c9635ae2f8f7530d04
Author: Shawn Smith <shawn.p.smith@gmail.com>
Date: Wed Dec 18 10:18:35 2013 -0800
encoding/json: add test for HTMLEscape
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/38220044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/aa20d2629284ee73637598c9635ae2f8f7530d04
元コミット内容
encoding/json: add test for HTMLEscape
R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/38220044
変更の背景
この変更の背景には、Webアプリケーションにおけるセキュリティのベストプラクティスがあります。JSONデータをHTMLドキュメント(特に <script> タグ内)に直接埋め込む場合、JSON文字列内にHTML特殊文字(例: <, >, &)や特定のUnicode文字(例: U+2028 LINE SEPARATOR, U+2029 PARAGRAPH SEPARATOR)が含まれていると、クロスサイトスクリプティング(XSS)脆弱性を引き起こす可能性があります。
encoding/json パッケージの HTMLEscape 関数は、このような潜在的なXSS攻撃を防ぐために設計されています。この関数は、JSONエンコードされたバイト列を処理し、HTMLコンテキストで安全に扱えるように、これらの特殊文字をUnicodeエスケープシーケンス(例: < を \u003c に)に変換します。
このコミットは、HTMLEscape 関数の既存の機能に対して、より包括的なテストカバレッジを提供することを目的としています。特に、HTMLタグ、アンパサンド、そしてJavaScript文字列リテラル内で改行として解釈されうるUnicode文字(U+2028, U+2029)が正しくエスケープされることを確認するためのテストが追加されています。これにより、将来的なリファクタリングや変更があった場合でも、この重要なセキュリティ機能が意図通りに動作し続けることが保証されます。
前提知識の解説
JSON (JavaScript Object Notation)
JSONは、人間が読み書きしやすく、機械が解析・生成しやすいデータ交換フォーマットです。JavaScriptのオブジェクトリテラルをベースにしていますが、言語に依存しないデータ形式として広く利用されています。Web APIのデータ送受信、設定ファイルの記述など、多岐にわたる用途で使われています。
HTML (HyperText Markup Language)
HTMLは、Webページの内容と構造を定義するためのマークアップ言語です。テキスト、画像、リンクなどを組み合わせてドキュメントを作成します。WebブラウザはHTMLを解釈し、視覚的にレンダリングします。
クロスサイトスクリプティング (XSS)
XSSは、Webアプリケーションの脆弱性の一つで、攻撃者が悪意のあるスクリプトをWebページに挿入し、そのスクリプトを他のユーザーのブラウザで実行させることを可能にします。これにより、セッションハイジャック、個人情報の窃取、Webサイトの改ざんなどが行われる可能性があります。
JSONデータをHTMLに埋め込む際に、JSON文字列内に <script> タグやイベントハンドラ属性(例: onerror)などのHTML特殊文字がエスケープされずに含まれていると、ブラウザがそれをHTMLとして解釈し、悪意のあるスクリプトが実行されてしまう可能性があります。
Unicodeエスケープシーケンス
Unicodeエスケープシーケンスは、Unicode文字をASCII文字の組み合わせで表現する方法です。JSONでは、\uXXXX の形式でUnicode文字を表現します。例えば、< は \u003c、> は \u003e、& は \u0026 とエスケープされます。これにより、これらの文字がHTMLパーサーによって特殊な意味を持つ文字として解釈されるのを防ぎます。
特に、U+2028 (LINE SEPARATOR) と U+2029 (PARAGRAPH SEPARATOR) は、JavaScriptの文字列リテラル内で改行として解釈される可能性があるため、JSONをJavaScriptコード内に埋め込む際にはエスケープすることが推奨されます。これにより、JavaScriptの構文エラーや意図しないコード実行を防ぐことができます。
技術的詳細
Goの encoding/json パッケージは、Goのデータ構造とJSONデータの間で変換を行うための機能を提供します。json.Marshal 関数はGoの値をJSONバイト列にエンコードし、json.Unmarshal 関数はJSONバイト列をGoの値にデコードします。
HTMLEscape 関数は、json.Encoder の内部で利用されるか、あるいはJSONバイト列をHTMLに安全に埋め込む必要がある場合に直接呼び出されることを想定しています。この関数は、入力されたバイト列を走査し、以下の文字を対応するUnicodeエスケープシーケンスに変換します。
<(小なり記号) ->\u003c>(大なり記号) ->\u003e&(アンパサンド) ->\u0026- U+2028 (LINE SEPARATOR) ->
\u2028 - U+2029 (PARAGRAPH SEPARATOR) ->
\u2029
これらのエスケープは、JSON文字列がHTMLの <script> タグ内に埋め込まれる際に特に重要です。例えば、var data = {"key": "value"}; のようにJSONをJavaScript変数に代入する場合、value にHTML特殊文字が含まれていると、ブラウザがJavaScriptコードではなくHTMLとして解釈してしまう可能性があります。HTMLEscape はこの問題を解決します。
追加されたテスト TestHTMLEscape は、具体的なJSON文字列 {"M":"<html>foo &\u2028 \u2029</html>"} を入力として使用しています。この文字列には、HTMLタグ (<html>, </html>)、アンパサンド (&)、そしてUnicodeの改行文字 (\u2028, \u2029) が含まれています。
期待される出力は、これらの文字がすべてUnicodeエスケープされた形式です: {"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}。
注目すべきは、\u2028 と \u2029 はJSONの仕様上はエスケープが必須ではありませんが、JavaScriptの文字列リテラル内で改行として解釈される可能性があるため、HTMLEscape 関数ではこれらもエスケープの対象としている点です。これは、JSONをHTMLに埋め込む際のセキュリティを最大化するための設計判断です。
テストは、bytes.Buffer を使用して HTMLEscape 関数の出力をキャプチャし、期待される出力と比較することで、エスケープ処理が正しく行われていることを検証しています。
コアとなるコードの変更箇所
変更は src/pkg/encoding/json/encode_test.go ファイルに集中しており、具体的には TestHTMLEscape という新しいテスト関数が追加されています。
--- a/src/pkg/encoding/json/encode_test.go
+++ b/src/pkg/encoding/json/encode_test.go
@@ -425,3 +425,13 @@ func TestIssue6458(t *testing.T) {
t.Errorf("Marshal(x) = %#q; want %#q", b, want)
}
}
+
+func TestHTMLEscape(t *testing.T) {
+ var b, want bytes.Buffer
+ m := `{"M":"<html>foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `</html>"}`
+ want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`))
+ HTMLEscape(&b, []byte(m))
+ if !bytes.Equal(b.Bytes(), want.Bytes()) {
+ t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes())
+ }
+}
コアとなるコードの解説
追加された TestHTMLEscape 関数は以下の要素で構成されています。
-
バッファの宣言:
var b, want bytes.BufferbはHTMLEscape関数の出力が書き込まれるバッファです。wantは期待される出力が書き込まれるバッファです。bytes.Bufferは可変長のバイトシーケンスを扱うための便利な型で、io.Writerインターフェースを実装しているため、HTMLEscapeの第一引数として渡すことができます。 -
入力文字列の定義:
m := `{"M":"<html>foo &` + "\xe2\x80\xa8 \xe2\x80\xa9" + `</html>"}`mはHTMLEscape関数に渡される元のJSON文字列です。<html>と</html>はHTMLタグです。&はアンパサンドです。\xe2\x80\xa8はUTF-8エンコードされたU+2028 (LINE SEPARATOR) です。\xe2\x80\xa9はUTF-8エンコードされたU+2029 (PARAGRAPH SEPARATOR) です。 これらは、HTMLEscapeがエスケープすべき文字の代表例です。
-
期待される出力の定義:
want.Write([]byte(`{"M":"\u003chtml\u003efoo \u0026\u2028 \u2029\u003c/html\u003e"}`))wantバッファには、mがHTMLEscapeによって正しく処理された場合に期待されるバイト列が書き込まれます。<は\u003cにエスケープされています。>は\u003eにエスケープされています。&は\u0026にエスケープされています。\u2028と\u2029は、入力文字列ではUTF-8バイト列として表現されていましたが、期待される出力ではUnicodeエスケープシーケンスとしてそのまま残されています。これは、HTMLEscapeがこれらの文字を特別にエスケープするのではなく、そのまま通過させる(ただし、JSONエンコーディングの文脈では既にエスケープされているか、あるいはJavaScriptの文脈で問題ない形式になっていることを期待する)ことを示唆しています。しかし、このテストの意図は、HTML特殊文字が正しくエスケープされることを確認することにあります。
-
HTMLEscape関数の呼び出し:HTMLEscape(&b, []byte(m))HTMLEscape関数が呼び出され、入力文字列mのバイト列がbバッファにエスケープされて書き込まれます。 -
結果の検証:
if !bytes.Equal(b.Bytes(), want.Bytes()) { t.Errorf("HTMLEscape(&b, []byte(m)) = %s; want %s", b.Bytes(), want.Bytes()) }bバッファに書き込まれた実際の出力と、wantバッファに格納された期待される出力をbytes.Equal関数で比較します。もし両者が一致しない場合、t.Errorfを呼び出してテスト失敗を報告し、実際の出力と期待される出力を表示します。
このテストは、HTMLEscape 関数がHTML特殊文字を適切にエスケープし、JSONデータがHTMLコンテキストで安全に埋め込めることを保証するための重要な役割を果たします。
関連リンク
- Go言語
encoding/jsonパッケージのドキュメント: https://pkg.go.dev/encoding/json - Go言語のテストに関するドキュメント: https://go.dev/doc/code#testing
- クロスサイトスクリプティング (XSS) について (OWASP): https://owasp.org/www-community/attacks/xss/
参考にした情報源リンク
- Go言語の公式ドキュメント
- OWASP (Open Web Application Security Project) のXSSに関する情報
- JSONの仕様 (RFC 8259)
- Unicodeの仕様
- Go言語の
encoding/jsonパッケージのソースコード (特にencode.goとencode_test.go) - Go言語の
bytesパッケージのドキュメント: https://pkg.go.dev/bytes - Go言語の
testingパッケージのドキュメント: https://pkg.go.dev/testing