LoginSignup
2

More than 3 years have passed since last update.

(JS)絶対にDeep Mergeを自力で実装しないでください

Posted at

tl;dr

  • JavaScriptでオブジェクトのdeep mergeをしたいならこの記事を読むか、あるいは最新版のlodashを使ってください
  • 自力で実装したdeep mergeを使う場合は、引数にユーザーの入力(JSONなど)を使わないでください

deep merge? なにそれ

オブジェクトの引数を再帰的に設定する関数で、lodashでいえばmerge関数とかに相当。

プロトタイプ汚染の闇

JavaScriptのオブジェクトには99%プロトタイプが存在します。偉い人はオブジェクトをObject.create(null)で作ったりしますが、そういう人は稀です。

プロトタイプの属性は、同名の属性が元のオブジェクトに存在しない場合のフォールバックとして使われます。たとえば、いつも使うhasOwnPropertyは、実はObjectのプロトタイプ内に存在するのです。

では、そのプロトタイプを書き換えられたらどうでしょうか。たとえば、hasOwnProperty1になったら、({}).hasOwnProperty("foo")といったコードがエラーを出します。もし認証系のコードで「isAdmin属性が存在すれば管理者だ」をconst isAdmin = ({}).isAdmin;のように実装していれば、立派な権限昇格です。これは「プロトタイプ汚染」(Prototype Pollution)と呼ばれます。

そして、プロトタイプ汚染が怖いのは、deep mergeという簡単な操作で、引き起こせるからです。

Deep Merge クイックテスト

もし下のオブジェクトを空のオブジェクトにdeep mergeして、Object.prototype.isAdminがセットされたら、プロトタイプ汚染成功です。(自分のコードでのみ試してください... 本番環境のAPIに投げるのはお勧めしません。)

{
  "__proto__": {"isAdmin": 1},
  "constructor": {"prototype": {"isAdmin": 1}}
}

安全なdeep merge

一番安全なのは、lodashのようなライブラリーを使うことです。なお、最新版を使うことをお勧めします。

それでも自力で実装したい場合は

  • __proto__constructorprototypeのキーが出てきたら無視する。
  • merge先のhasOwnPropertyの結果がfalseの場合は、空のオブジェクトを作成する。(typeof merge先 === "object"のみだと不十分。)

のような安全策を取らないといけません。荒技としてはObject.freeze(Object.prototype)で汚染をできないようにすることもできますが、あくまで荒技です。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2