[インデックス 12246] ファイルの概要
このコミットは、Go言語の encoding/json
パッケージにおける MarshalForHTML
関数の削除と、それに伴う gofix
ツールの更新を目的としています。これにより、JSONのHTMLエスケープ処理の責任が変更され、将来的な二重エスケープの問題を回避し、よりシンプルで安全なJSONマーシャリングの利用を促進します。
コミット
commit 9dd746c4cb09b65128d0dd432b58c324151910bf
Author: David Symonds <dsymonds@golang.org>
Date: Tue Feb 28 11:41:16 2012 +1100
encoding/json: drop MarshalForHTML; gofix calls to Marshal.
I've elected to omit escaping the output of Marshalers for now.
I haven't thought through the implications of that;
I suspect that double escaping might be the undoing of that idea.
Fixes #3127.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5694098
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/9dd746c4cb09b65128d0dd432b58c324151910bf
元コミット内容
encoding/json: drop MarshalForHTML; gofix calls to Marshal.
I've elected to omit escaping the output of Marshalers for now.
I haven't thought through the implications of that;
I suspect that double escaping might be the undoing of that idea.
Fixes #3127.
R=golang-dev, r
CC=golang-dev
https://golang.org/cl/5694098
変更の背景
この変更の背景には、Go言語の encoding/json
パッケージにおけるJSONのHTMLエスケープ処理の複雑さと、それに伴う潜在的な問題がありました。特に、MarshalForHTML
関数はJSONデータをHTMLに安全に埋め込むためにHTMLエスケープを施していましたが、これが二重エスケープを引き起こす可能性や、開発者が意図しない挙動を招くリスクがありました。
コミットメッセージに記載されている Fixes #3127
は、この変更が解決しようとしている具体的な問題を示唆しています。GoのIssue #3127は、「json: MarshalForHTML is not sufficient for embedding in HTML」というタイトルで、MarshalForHTML
がHTMLにJSONを埋め込む際に常に安全ではないという問題提起がされていました。具体的には、MarshalForHTML
がエスケープする文字が <
、>
、&
のみであり、'
や "
といったHTML属性値やJavaScript文字列リテラル内で特別な意味を持つ文字がエスケープされないため、XSS(クロスサイトスクリプティング)脆弱性の原因となる可能性が指摘されていました。
このコミットでは、MarshalForHTML
を削除することで、JSONのHTMLエスケープの責任をアプリケーション開発者側に移譲し、より柔軟かつ安全な方法でJSONをHTMLに埋め込むことを促しています。また、gofix
ツールを更新することで、既存のコードベースが MarshalForHTML
から Marshal
へとスムーズに移行できるように支援しています。コミットメッセージにある「I've elected to omit escaping the output of Marshalers for now. I haven't thought through the implications of that; I suspect that double escaping might be the undoing of that idea.」という記述は、JSONマーシャラ自体がHTMLエスケープを行うことの複雑さと、二重エスケープのリスクを認識し、その責任を分離する方向性を示しています。
前提知識の解説
JSON (JavaScript Object Notation)
JSONは、人間が読んで理解しやすく、機械が生成・解析しやすいデータ交換フォーマットです。JavaScriptのオブジェクトリテラルをベースにしており、キーと値のペアの集合(オブジェクト)や、値の順序付きリスト(配列)でデータを表現します。Web APIなどで広く利用されています。
Go言語の encoding/json
パッケージ
Go言語の標準ライブラリに含まれる encoding/json
パッケージは、Goのデータ構造とJSONデータの間で変換(マーシャリングとアンマーシャリング)を行う機能を提供します。
json.Marshal(v interface{}) ([]byte, error)
: Goの値をJSON形式のバイトスライスに変換(マーシャリング)します。json.Unmarshal(data []byte, v interface{}) error
: JSON形式のバイトスライスをGoの値に変換(アンマーシャリング)します。json.HTMLEscape(dst *bytes.Buffer, src []byte)
: JSONエンコードされたバイトスライス内の<
、>
、&
文字を\u003c
、\u003e
、\u0026
にエスケープし、HTMLの<script>
タグ内に安全に埋め込めるようにします。
HTMLエスケープとXSS (Cross-Site Scripting)
HTMLエスケープとは、HTMLドキュメント内で特別な意味を持つ文字(例: <
, >
, &
, '
, "
)を、その文字自体として表示されるように変換する処理です。例えば、<
は <
に、>
は >
に変換されます。
XSSは、ウェブアプリケーションの脆弱性の一種で、攻撃者が悪意のあるスクリプトをウェブページに挿入し、そのスクリプトがユーザーのブラウザで実行されることで発生します。HTMLエスケープが不十分な場合、ユーザーからの入力や外部から取得したデータがそのままHTMLに出力され、攻撃者が挿入したスクリプトが実行されてしまう可能性があります。
JSONデータをHTMLの <script>
タグ内に埋め込む場合、JSONデータ内にHTMLのタグを閉じる </script>
や、JavaScriptの文字列リテラルを閉じる "
や '
が含まれていると、意図しないスクリプトの実行やHTML構造の破壊につながる可能性があります。そのため、JSONデータをHTMLに埋め込む際には、適切なHTMLエスケープが不可欠です。
gofix
ツール
gofix
は、Go言語のツールチェーンに含まれるコマンドラインツールです。Go言語のバージョンアップに伴うAPIの変更や非推奨化に対応するため、古いGoコードを新しいAPIに自動的に書き換える機能を提供します。これにより、開発者は手動でコードを修正する手間を省き、スムーズに新しいGoバージョンに移行できます。gofix
は、Goのソースコードを解析し、定義されたルールに基づいてコードを変換します。
技術的詳細
このコミットの技術的詳細は、encoding/json
パッケージから MarshalForHTML
関数を削除し、その責任を呼び出し元に移譲することにあります。
MarshalForHTML
は、内部で json.Marshal
を呼び出した後、結果に対して json.HTMLEscape
を適用していました。しかし、前述のIssue #3127で指摘されたように、HTMLEscape
がエスケープする文字は <
、>
、&
のみであり、HTML属性値やJavaScript文字列リテラル内で特別な意味を持つ "
や '
はエスケープされませんでした。このため、MarshalForHTML
を使用しても、常に安全にJSONをHTMLに埋め込めるわけではありませんでした。
開発者が MarshalForHTML
を使用する際に、その不完全なエスケープ処理を誤解し、結果としてXSS脆弱性を生み出すリスクがありました。このコミットでは、MarshalForHTML
を削除することで、この誤解の可能性を排除し、開発者自身がJSONをHTMLに埋め込む際のセキュリティ対策を適切に行うよう促しています。
コミットメッセージにある「I've elected to omit escaping the output of Marshalers for now. I haven't thought through the implications of that; I suspect that double escaping might be the undoing of that idea.」という記述は、JSONマーシャリングの過程でHTMLエスケープを行うことの難しさを示しています。もしマーシャラがHTMLエスケープを完全に担当しようとすると、様々なコンテキスト(HTMLのどこに埋め込むか、JavaScriptのどの部分に埋め込むかなど)を考慮する必要があり、非常に複雑になります。また、アプリケーション側で既にエスケープ処理を行っている場合、マーシャラがさらにエスケープを行うと二重エスケープが発生し、データが正しく表示されなくなる問題も生じます。
したがって、この変更は、JSONのマーシャリングとHTMLエスケープの責任を明確に分離し、それぞれの役割をシンプルに保つことを目指しています。これにより、開発者はJSONデータを生成し、その後にHTMLに埋め込む必要がある場合に、そのコンテキストに応じた適切なエスケープ処理を明示的に適用するようになります。
gofix
ツールの更新は、このAPI変更に対する後方互換性を提供するための重要なステップです。既存のGoコードベースで json.MarshalForHTML
が使用されている場合、gofix
を実行することで自動的に json.Marshal
に書き換えられます。これにより、開発者は手動で大量のコードを修正することなく、新しいAPIに移行できます。
コアとなるコードの変更箇所
このコミットでは、以下の4つのファイルが変更されています。
-
src/cmd/fix/go1rename.go
:gofix
ツールのリネームルールが定義されているファイルです。json.MarshalForHTML
をjson.Marshal
にリネームするルールが追加されました。
--- a/src/cmd/fix/go1rename.go +++ b/src/cmd/fix/go1rename.go @@ -38,6 +38,12 @@ var go1renameReplace = []rename{ Old: "*des.TripleDESCipher", New: "cipher.Block", }, + { + OldImport: "encoding/json", + NewImport: "", + Old: "json.MarshalForHTML", + New: "json.Marshal", + }, { OldImport: "net/url", NewImport: "",
-
src/cmd/fix/go1rename_test.go
:gofix
ツールのリネームルールのテストファイルです。json.MarshalForHTML
がjson.Marshal
に正しくリネームされることを確認するテストケースが追加されました。
--- a/src/cmd/fix/go1rename_test.go +++ b/src/cmd/fix/go1rename_test.go @@ -25,6 +26,7 @@ var ( _ *aes.Cipher _ *des.Cipher _ *des.TripleDESCipher +_ = json.MarshalForHTML _ = aes.New() _ = url.Parse _ = url.ParseWithReference @@ -48,6 +51,7 @@ var ( _ cipher.Block _ cipher.Block _ cipher.Block +_ = json.Marshal _ = aes.New() _ = url.Parse _ = url.Parse
-
src/pkg/encoding/json/decode_test.go
:encoding/json
パッケージのデコード関連のテストファイルです。TestHTMLEscape
関数が削除されました。このテストはMarshalForHTML
の挙動を検証していましたが、関数が削除されるため不要になりました。
--- a/src/pkg/encoding/json/decode_test.go +++ b/src/pkg/encoding/json/decode_test.go @@ -239,16 +239,6 @@ func TestEscape(t *testing.T) { }\n}\n\n-func TestHTMLEscape(t *testing.T) {\n-\tb, err := MarshalForHTML(\"foobarbaz<>&quux\")\n-\tif err != nil {\n-\t\tt.Fatalf(\"MarshalForHTML error: %v\", err)\n-\t}\n-\tif !bytes.Equal(b, []byte(`\"foobarbaz\\u003c\\u003e\\u0026quux\"`)) {\n-\t\tt.Fatalf(\"Unexpected encoding of \\\"<>&\\\": %s\", b)\n-\t}\n-}\n-\n // WrongString is a struct that's misusing the ,string modifier.\n type WrongString struct {\n \tMessage string `json:\"result,string\"`
-
src/pkg/encoding/json/encode.go
:encoding/json
パッケージのエンコード関連の主要ファイルです。MarshalForHTML
関数が完全に削除されました。
--- a/src/pkg/encoding/json/encode.go +++ b/src/pkg/encoding/json/encode.go @@ -123,17 +123,6 @@ func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) { return buf.Bytes(), nil }\n\n-// MarshalForHTML is like Marshal but applies HTMLEscape to the output.\n-func MarshalForHTML(v interface{}) ([]byte, error) {\n-\tb, err := Marshal(v)\n-\tif err != nil {\n-\t\treturn nil, err\n-\t}\n-\tvar buf bytes.Buffer\n-\tHTMLEscape(&buf, b)\n-\treturn buf.Bytes(), nil\n-}\n-\n // HTMLEscape appends to dst the JSON-encoded src with <, >, and &\n // characters inside string literals changed to \\u003c, \\u003e, \\u0026\n // so that the JSON will be safe to embed inside HTML <script> tags.\n ```
コアとなるコードの解説
src/cmd/fix/go1rename.go
と src/cmd/fix/go1rename_test.go
これらのファイルへの変更は、gofix
ツールが json.MarshalForHTML
の使用を検出し、それを json.Marshal
に自動的に書き換えるためのルールを追加しています。
go1rename.go
に追加されたrename
構造体は、OldImport
がencoding/json
で、Old
関数がjson.MarshalForHTML
の場合、New
関数をjson.Marshal
に変更することを定義しています。NewImport
が空文字列であることは、インポートパス自体は変更しないことを意味します。go1rename_test.go
に追加されたテストケースは、このリネームルールが正しく機能することを確認します。テストコード内でjson.MarshalForHTML
を使用している箇所が、gofix
適用後にjson.Marshal
に変換されることを検証しています。これにより、既存のコードベースが新しいAPIにスムーズに移行できることが保証されます。
src/pkg/encoding/json/decode_test.go
TestHTMLEscape
関数の削除は、MarshalForHTML
関数が削除されたことに直接関連しています。TestHTMLEscape
は MarshalForHTML
の挙動をテストするためのものでしたが、その対象となる関数がなくなったため、テストも不要となりました。これは、コードベースのクリーンアップと、存在しない機能に対するテストの削除を意味します。
src/pkg/encoding/json/encode.go
このファイルからの MarshalForHTML
関数の削除は、このコミットの最も重要な変更点です。
MarshalForHTML
は、JSONデータをHTMLに安全に埋め込むことを意図していましたが、そのエスケープ処理が不完全であったため、かえって開発者に誤解を与え、セキュリティ上の問題を引き起こす可能性がありました。この関数の削除により、encoding/json
パッケージは純粋なJSONマーシャリングの機能に特化し、HTMLエスケープの責任は、JSONデータを利用するアプリケーション側が、その利用コンテキスト(HTMLのどこに埋め込むか、JavaScriptの文字列リテラルとして使用するかなど)に応じて適切に行うべきであるという設計思想が明確になりました。
HTMLEscape
関数自体は残されていますが、これは MarshalForHTML
の内部で使われていたものであり、開発者が明示的に呼び出すことで、特定のHTMLエスケープ処理を行うことができます。しかし、このコミットの意図は、MarshalForHTML
のような「便利だが不完全な」ラッパー関数を削除し、開発者がセキュリティを意識した上で適切なエスケープ処理を自ら選択・実装することを促すことにあります。
関連リンク
- GitHubコミットページ: https://github.com/golang/go/commit/9dd746c4cb09b65128d0dd432b58c324151910bf
- Go CL (Code Review) ページ: https://golang.org/cl/5694098
- Go Issue 3127: json: MarshalForHTML is not sufficient for embedding in HTML: https://github.com/golang/go/issues/3127
参考にした情報源リンク
- Go言語
encoding/json
パッケージ公式ドキュメント: https://pkg.go.dev/encoding/json - Go言語
cmd/gofix
ドキュメント (Go 1.x): https://go.dev/doc/go1.html#gofix (Go 1.xのリリースノートにgofixに関する記述があります) - Cross-site scripting (XSS) - OWASP Cheat Sheet Series: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html
- JSON in HTML script tags considered dangerous - Stack Overflow: https://stackoverflow.com/questions/1586754/json-in-html-script-tags-considered-dangerous
- HTML Escaping - Wikipedia: https://en.wikipedia.org/wiki/HTML_escaping