
TC39 proposal for mitigating prototype pollution
著者: Santiago Díaz (Google)
チャンピオン: Shu-yu Guo (Google)
ステージ: 1
この提案は、プロトタイプ汚染として知られる言語レベルの脆弱性を、freezeプリミティブを補完するメカニズムと、ほとんどのコードベースをそれと互換性を持たせるためのメカニズムによって緩和しようとするものです。これは、プロトタイプをリフレクションAPIを通してのみ利用可能にするオプトイン機能を説明します。これにより、obj[key]という文はプロトタイプにアクセスできなくなります。この機能と互換性のあるコードベースは、プロトタイプの使い方に関してより_意図的_になります。
PPの脆弱性は、攻撃者が実行時に制御していない、またはアクセスできないオブジェクトを操作することを可能にします。この「不気味な遠隔作用」プリミティブは、他のオブジェクトの形状を変更し、そのプロパティを上書きするために使用でき、それによってランタイム内のオブジェクトを汚染します。
汚染されたオブジェクトは、そうでなければ安全/正しいコードの基礎となる前提を無効化し、任意のコード実行やJSコードベースにおける幅広いその他のセキュリティ問題につながる可能性があります。プロトタイプ汚染のバグは、Webアプリケーションで頻繁に発生しますが、Web以外のJSランタイムにも影響を与えます。
JSのオブジェクトプロパティは、それを参照できる任意のコードによって書き換え可能です。特に、多くのオブジェクトが共有プロパティに依存している場合、そのうちの1つが他のすべてに変更を加えることができます。
PPの特別な特性は、それがデータのみの攻撃であり、純粋にデータを通じてコード実行を達成できることです。例えば、次の脆弱なコードと対応するエクスプロイトを参照してください:
// source is attacker-controlled
function merge(target, source) {
for (let key in source) {
if (typeof source[key] === 'object') {
if(target[key] === undefined) {
target[key] = {};
}
target[key] = merge(target[key], source[key]);
} else {
target[key] = source[key];
}
}
return target;
}
// User input comes as a string
const userSuppliedObj = JSON.parse('{"__proto__": {"polluted": true}}');
// Trigger prototype pollution
merge({}, userSuppliedObj);
// Create a brand new object
const newObj = {};
// Has polluted property
console.log(newObj.polluted); // true
エクスプロイトは、_外部のコードを一切注入せずに_新しいオブジェクトの生成を汚染できることに注意してください。
この特別な特性のため、コンテンツセキュリティポリシーやTrusted Typesのようなコード実行問題に対する最新の緩和策は、_コードの来歴_の強制に焦点を当てているため、PPを防ぐには不十分です。
注記: データのみの攻撃は、VM上で実行されているコードが信頼されており、任意のコード実行がセキュリティに影響を与える状況に関連します。
freeze、seal、preventExtensionsの問題既存の凍結プリミティブには、広く採用されにくい重大な設計上の問題があります。これらはエキスパートユーザーには有用かもしれませんが、プロトタイプが変更可能であることを合理的に期待している大多数の開発者には適していません:
Freeze APIは、オーバーライドの誤りと他の不整合に悩まされており、既存のコードベースにバグを引き起こし、例外を投げたり、さらに悪いことにスロッピーモードでは_暗黙的に失敗_したりします。オーバーライドの誤りに関する以前の調査では、ストリクトモードの約10%のコードベースとスロッピーモードの20%でオーバーライドの誤りが発生すると結論付けられました。この調査はその直後に中止されました。
Freeze APIは、開発者がセキュリティの専門家であることを前提に、安全なコードベースを維持するためにどのプロトタイプを凍結すべきかを知るという重い責任を開発者に課します。これらのAPIは、セキュリティの_何を_ではなく_どのように_を記述します。Objectを凍結するだけでは明らかに不十分です。なぜなら、多くのエクスプロイトがArrayを悪用するからです。Error、Date、Reflect、Proxyはどうでしょうか? あるいは将来の組み込み型は? Freeze APIはこれらの問いに答えを提供しません。
Freeze APIは安定した凍結時点、つまりプロトタイプが確定し凍結できるランタイム上の固定された瞬間を想定しています。実際には、この時点は揮発性であり、活発に開発されているコードベースでは時間とともに変化します。今日の多くのアプリケーションでそのような時点を見つけることは可能ですが、新しい依存関係、ポリフィル、コード構造の変更、ホットスワップや開発者ツールなどのパワフルな機能の追加により、凍結時点は変化し続ける標的となります。
Freeze APIはプロトタイプチェーン全体を保護できません。JSでは、オブジェクトはいつでもプロトタイプチェーンに追加または削除できます。チェーン全体を保護するには、チェーンに追加されたオブジェクトを常に凍結することを忘れないようにする必要があり、これはエラーを起こしやすいプロセスです。チェーンから削除された場合、それらを凍結解除することはできなくなります。
一言で言えば: プロトタイプをリフレクションAPIにのみ公開する機能です。プロトタイプが__proto__やprototypeのようなプロパティを通じて利用可能でなければ、データのみの攻撃にさらされることはありません。
これは例を通して理解するのが最も良いでしょう: 文obj[one][two] = valueはobj.__proto__.pollutedを通じてPPに対して脆弱です。Object.prototype.__proto__プロパティを削除すれば、同じ文はもはや脆弱ではありません。なぜなら、プロトタイプに到達する唯一の他の方法であるobj.constructor.prototype.pollutedが当てはまらないからです。prototypeコンストラクタは削除できないことに注意してください。
この提案は、リフレクションAPIを提供し、プロトタイププロパティを削除する新しいオプトインのカプセル化機能を作成することで実装できます。各ステップの説明は次のとおりです。
__proto__は削除可能なレガシープロパティ名ですが、その背後にある内部スロットはObject/Reflect.getPrototypeOfを通じて読み取り、Object/Reflect.setPrototypeOfを通じて書き込むことができ、これは単に、すでに実行中のコードに対してこのプロパティをアクセス可能にし続けることになります。
我々はprototypeのための新しいAPI、例えばgetClassPrototypeOfとsetClassPrototypeOfの作成を提案します。これにより、この特別なプロパティの動作方法を一切変えず、VMをサポートしたまま、このプロパティ名を削除できるようになります。
リフレクションAPIはポリフィル可能であり、強化されたコードベースが古いバージョンを含むすべてのブラウザで動作することを可能にします。
新しいオプトインの「カプセル化機能」では、プロトタイプスロットのゲッター関数とセッター関数のためにプロパティ名が作成されません。これは、それらのプロパティへの参照が代わりにリフレクションAPIを使用できるようになったため、現在可能になっています。
この機能は帯域外フラグを通じて有効になります:
X-Encapsulate-Prototype: trueのようなHTTPヘッダーを通じて--encapsulate-prototypeのような機能フラグを通じてカプセル化が_無効_の場合、プロトタイプはプロパティとリフレクションAPIの両方を介して利用可能です。
カプセル化が_有効_の場合、__proto__とprototypeの両方が削除され、プロトタイプはリフレクションAPIを介してのみ利用可能になります。
カプセル化には、次の自動リファクタリング機能も含まれます:
カプセル化が有効な場合、JSエンジンが新しいソースコードを読み込む際、パースフェーズに追加のステップが有効になり、プロトタイププロパティへのすべてのドット記法が、それらに対するリフレクションAPIの呼び出しであるかのように登録されます。このステップは効率的に実装でき、サードパーティ、推移的、または動的に読み込まれる依存関係を持つコードベースをカプセル化と互換性を持たせることができます。
将来的には、この変更はprototypeを非推奨としてマークする道を開くでしょう。
カプセル化が有効な場合、プロトタイププロパティは単にundefinedになることもできますが、読み取り/書き込みの試みがあった場合にエラーを投げることもできます。これは、より速く、より明確に失敗することを意味し、リフレクションAPI/カプセル化への移行をテスト可能にします。
これは、eval関数がコンテンツセキュリティポリシーの下で例外を投げるのと同じように、ホストフックを使用して__proto__とprototypeのゲッターとセッターをカプセル化に応じて条件付きにすることを意味します。
プロトタイプを参照するために_計算プロパティアクセスに依存する_コードは、カプセル化や自動リファクタリングと互換性がありません。プロトタイプを使用する場合に明示的に参照するようにリファクタリングする必要があります。このリファクタリングは実際にコードが意図を表現するようにし、危険なパターンを静的解析で可視化します。実際には、この特性を持つコードベースは通常、リフレクションフレームワーク、デバッグツール、その他のリフレクションを多用するユースケースであり、プロトタイプの使い方を認識している可能性が最も高いです。
prototypeという単語を使って独自のプロパティを定義するコードベースは互換性がありません。このようなコードベースは、このプロパティが常にブラケット記法で設定/取得される場合、カプセル化と互換性を持たせることができます。歴史的に、またHTTP Archiveのクエリに基づくと、この理由で互換性のないコードベースの割合は小さいです。
constructorプロパティへの一部の変更も、不気味な遠隔作用を引き起こす可能性があります。私たちの調査中に、これによって影響を受ける実際の脆弱性は見つかりませんでした。
この攻撃が成立するためのハードルは非常に高いです: PPと同様に、任意のプロパティを書き込むためのガジェットと読み取るためのガジェットの両方を持つアプリケーションを見つけなければなりません。しかし、コンストラクタ汚染では、読み取りガジェットはpollutedではなくconstructor.pollutedから読み取らなければなりません。これにより、有用なガジェットの数が劇的に減少します。
一部の圧縮されたJSは、静的プロパティアクセスが計算アクセスにミニファイされる可能性があるため、カプセル化モードと互換性がない場合があります。我々はHTTP Archiveを照会して、実際の状況を推定しました。次の表は、この動作を持つページが、デスクトップブラウザでクロールされたすべてのページにおいて、過去12か月間一貫して1%未満であることを示しています:
Googleは、脆弱性報奨金プログラムに提出されたバグの増加傾向を確認しています: 2020年に1件、2021年に3件、2022年はこれまでに5件です。内部調査ではさらにいくつかを特定しています。
脆弱性の例は次のとおりです:
JavaScriptアプリケーションがより多くの環境(例: Electron、Cloudflare Workersなど)に展開されるにつれて、脆弱なアプリケーションの数は増加すると予想されます。したがって、すべての環境での攻撃を緩和するには、言語レベルの解決策が必要です。
| Table | Documents accessing __proto__ or constructor dynamically | Total number of crawled documents | Ratio |
|---|
| 2023_03_01_desktop | 5,407,936 | 609,469,458 | 0.89% |
| 2023_02_01_desktop | 4,842,383 | 549,089,708 | 0.88% |
| 2023_01_01_desktop | 5,283,826 | 589,519,160 | 0.90% |
| 2022_12_01_desktop | 5,161,471 | 577,073,883 | 0.89% |
| 2022_11_01_desktop | 5,023,169 | 561,726,239 | 0.89% |
| 2022_10_01_desktop | 4,393,377 | 476,880,624 | 0.92% |
| 2022_09_01_desktop | 4,239,257 | 466,278,762 | 0.91% |
| 2022_08_01_desktop | 4,259,814 | 463,784,047 | 0.92% |
| 2022_07_01_desktop | 3,011,137 | 339,468,615 | 0.89% |
| 2022_06_01_desktop | 2,301,317 | 257,501,222 | 0.89% |
| 2022_04_01_desktop | 2,368,577 | 263,144,657 | 0.90% |
| 2022_03_01_desktop | 2,319,518 | 259,249,013 | 0.89% |