Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 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ドキュメント内で特別な意味を持つ文字(例: <, >, &, ', ")を、その文字自体として表示されるように変換する処理です。例えば、<&lt; に、>&gt; に変換されます。

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つのファイルが変更されています。

  1. src/cmd/fix/go1rename.go: gofix ツールのリネームルールが定義されているファイルです。

    • json.MarshalForHTMLjson.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: "",
    
  2. src/cmd/fix/go1rename_test.go: gofix ツールのリネームルールのテストファイルです。

    • json.MarshalForHTMLjson.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
    
  3. 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\"`
    
  4. 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.gosrc/cmd/fix/go1rename_test.go

これらのファイルへの変更は、gofix ツールが json.MarshalForHTML の使用を検出し、それを json.Marshal に自動的に書き換えるためのルールを追加しています。

  • go1rename.go に追加された rename 構造体は、OldImportencoding/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 関数が削除されたことに直接関連しています。TestHTMLEscapeMarshalForHTML の挙動をテストするためのものでしたが、その対象となる関数がなくなったため、テストも不要となりました。これは、コードベースのクリーンアップと、存在しない機能に対するテストの削除を意味します。

src/pkg/encoding/json/encode.go

このファイルからの MarshalForHTML 関数の削除は、このコミットの最も重要な変更点です。

MarshalForHTML は、JSONデータをHTMLに安全に埋め込むことを意図していましたが、そのエスケープ処理が不完全であったため、かえって開発者に誤解を与え、セキュリティ上の問題を引き起こす可能性がありました。この関数の削除により、encoding/json パッケージは純粋なJSONマーシャリングの機能に特化し、HTMLエスケープの責任は、JSONデータを利用するアプリケーション側が、その利用コンテキスト(HTMLのどこに埋め込むか、JavaScriptの文字列リテラルとして使用するかなど)に応じて適切に行うべきであるという設計思想が明確になりました。

HTMLEscape 関数自体は残されていますが、これは MarshalForHTML の内部で使われていたものであり、開発者が明示的に呼び出すことで、特定のHTMLエスケープ処理を行うことができます。しかし、このコミットの意図は、MarshalForHTML のような「便利だが不完全な」ラッパー関数を削除し、開発者がセキュリティを意識した上で適切なエスケープ処理を自ら選択・実装することを促すことにあります。

関連リンク

参考にした情報源リンク