プロトタイプ汚染攻撃(prototype pollution attack)って何?
一言で言えば、JavaScriptはプロトタイプベースの言語であるため、プリミティブ型およびオブジェクトのインスタンスは原則プロトタイプオブジェクトを参照していると言う特性を悪用した攻撃です。ECMAScript2015 で仕様に加わりましたが仕様が理解されないままJavascriptの利用だけ広がった結果悪用が進んでいる脆弱性です。サーバサイドでJavascriptが利用されることになりより深刻な脆弱性となりつつあるのでこの記事を見たご縁で一読いただけますと幸いです。
JavaScriptはプロトタイプベースの言語であること、プロトタイプオブジェクトの概念が理解されていないことが悪用されている
まずプロトタイプベースの言語(Javascript)とクラスベース言語(Java や C++)の違いを理解する
JavaScript では、クラスではなくプロトタイプに基づいたオブジェクトとなります。この違いを理解するためにJava や C++ といったクラスベースのオブジェクト指向言語におけるクラスとインスタンスと比較して説明するのが良いでしょう。クラスはオブジェクトの集まりを特徴付けるすべてのプロパティを定義します。クラスを具体化し親クラスのプロパティを保持したものをインスタンスとします。
一方、プロトタイプベースの言語では、この区別がなくオブジェクトだけがあります。そして新しいオブジェクトの初期プロパティの取得元になるテンプレートとして使用されるオブジェクトをプロトタイプオブジェクト (prototypical object) と呼ばれます。この結果、どのオブジェクトも別のオブジェクトに対するプロトタイプとして関連づけることができます。さらにオブジェクト作成時にも実行時問わずどのオブジェクトも独自のプロパティを指定できます。
クラスベース言語ではクラスをコンパイル時に生成し、コンパイル時または実行時にクラスのインスタンスを作成します。クラス定義後に、そのクラスのプロパティの数や型を変更することはできません。
これに対し JavaScript では、どのオブジェクトでも実行時にプロパティの追加や削除ができます。ある一連のオブジェクトでプロトタイプとして使用されているオブジェクトにプロパティを追加すると、それをプロトタイプとするオブジェクトにも新しいプロパティが追加できます。
クラスチェーンに従ってプロパティを継承するかプロトタイプチェーンに従ってプロパティを継承するかの違いが、プロトタイプ汚染攻撃という悪用の可能性を生み出します。
プロトタイプベースの言語はどの作成・実行どのタイミングでも別のオブジェクトに対するプロトタイプとして関連づけプロパティの追加・削除が行えるため、クラスベース言語のインスタンスのように生成元のプロパティのままが保持されているとは限らないということに注意する
プロトタイプ汚染攻撃(prototype pollution attack)はプロトタイプ言語の特性を悪用したもの
以下のコードを実行するとmy_function.polluted
の値は2021と出力されます。これはObject.prototype.polluted とevil_function.__proto__.pollutedが同一と見做されevil_functionの定義からmy_functionのプロパティが変更できたことになります。これはJavascriptがプロトタイプ言語だと理解していれば当たり前の動作ですが、仕様を理解していない場合、信用できないプロパティを処理に使用してしまう可能性が考えられます。この悪用方法をプロトタイプ汚染攻撃(prototype pollution attack)と言います。
const evil_function = {}
evil_function.__proto__.polluted = 2021
const my_function = {}
console.log(my_function.polluted)
__proto__ はObject.prototype と同一、プロパティはJavascriptでは流動的なものと考え値を信用せず処理前に必ずチェックする
プロトタイプ汚染攻撃(prototype pollution attack)の対策
対策は①プロパティを信用せず処理に使用しないか、②徹底的に検証、プロパティへのアクセス制限、変更不可にするなど使用するプロパティの悪用可能性を排除するかの選択と考えられます。①の使用しないはそうできるなら解説はいらないかと存じますので、②について見ていきましょう。
Object.freezeで変更不可にする
具体的にはObject.freeze(Object.prototype);を記載します。有効ですが何かしら動かなくなる可能性が否定できません。
__proto__, __defineGetter__, __defineSetter__, __lookupGetter__, __lookupSetter__の読み取り・書き込みのアクセスを禁止する
有効ですが何かしら動かなくなる可能性が否定できません。
対策は変更不可とアクセス制限の2つのほか、連想配列の場合はMapを利用するなど安全な関数に置き換える方法があります。
Javascriptはとっつきやすく利用目的を達成するだけなら深い理解が必要ない場合も多いかと思われますが、企業で提供するサービスの場合はまず言語の特性など基本的な特徴に関しては押さえ、大きな問題を引き起こさないように注意しましょう。
ここまで読んで頂いてありがとうございます。良い開発ライフを
プロトタイプ言語のプロパティは書き換わる(信用できない)前提で開発を行い、制御が困難と感じたら特に企業で提供するサービスでは無理せず代替手段を検討する
以上です。ご参考になりましたら幸いです。