0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【セキュリティ】Prototype Pollutionを引き起こす「Property Injection」解説

Posted at

はじめに

― Recursive Merge と Clone 機能が招く深刻な脆弱性 ―

現代のJavaScriptアプリケーションでは、
設定マージ、オブジェクトの複製、ユーザーデータの更新など、
オブジェクトをマージする処理」が数えきれないほど登場します。

…が、ここにこそ悪魔が潜んでいます。

Prototype Pollution(プロトタイプ汚染)は、
“アプリ内のすべてのオブジェクト” に影響を与えうる 非常に危険な脆弱性です。
今回は、実際のクローンアルバム機能を例に、攻撃方法から影響範囲、そして対策までを徹底的に解説します。


1. Property Injectionとは何か

Property Injectionとは、アプリが持つ

  • recursive merge(再帰的マージ)
  • deep clone(深い複製)

といった便利機能を悪用して、
攻撃者が任意のプロパティをサーバ側オブジェクトに注入する攻撃手法です。

入力キーを正しくフィルタしていない場合、
攻撃者は __proto__constructor などの 特殊キー を注入し、
アプリケーション全体のプロトタイプチェーンを書き換えることができます。


2. 脆弱な recursiveMerge の例

まずは典型的な「設定マージ関数」から。

function recursiveMerge(target, source) {
    for (let key in source) {
        if (source[key] instanceof Object) {
            if (!target[key]) target[key] = {};
            recursiveMerge(target[key], source[key]);
        } else {
            target[key] = source[key];
        }
    }
}

攻撃者は次のリクエストを送るだけで Prototype Pollution を起こせます:

{
  "__proto__": { "newProperty": "value" }
}

なぜ?
target["__proto__"] = {...} は結果的にこうなります:

Object.prototype.newProperty = "value";

全オブジェクトに newProperty が生える。チーン。


3. 実例:Clone Album 機能の破壊的コンボ

攻撃の舞台となるのは、次のような「アルバム複製機能」。

クライアント側(ユーザーが入力する部分)

<form action="/clone-album/1" method="post">
    <input type="text" name="newAlbumName" placeholder="Enter new album name">
    <button type="submit">Clone Album</button>
</form>

見た目はただのテキスト入力。しかしサーバ側は…


サーバ側の処理(核心)

const payload = JSON.parse(newAlbumName);
merge(clonedAlbum, payload);

攻撃者は「アルバム名」に JSON を入れられる状態。

さらに merge() も完全に無防備:

function merge(to, from) {
  for (let key in from) {
    if (typeof to[key] == "object" && typeof from[key] == "object") {
      merge(to[key], from[key]);
    } else {
      to[key] = from[key];
    }
  }
}

つまり…

攻撃者はアルバム名に以下を入力できる:

{"__proto__": { "newProperty": "hacked" }}

サーバ側はそれを JSON として受理、merge、そして…

clonedAlbum.__proto__.newProperty = "hacked"

結果:

friend オブジェクト全員が newProperty を持つようになる。


4. どうして全オブジェクトに影響するのか?

JavaScript は「プロトタイプチェーン」という仕組みでプロパティを探索します。

  1. まず自分自身を見る
  2. 無ければ __proto__ を見る
  3. さらに上のプロトタイプへ…

つまり prototype を汚染すると:

  • 既存のすべての friend オブジェクト
  • 未来に作られる friend オブジェクト

が等しく影響を受ける。


5. なぜ画面にも newProperty が出てしまうのか?

テンプレート側のコードがこちら:

<% for (let key in friend) { %>
  <p><%= key %>: <%= friend[key] %></p>
<% } %>

for...in は:

  • 自身のプロパティだけでなく
  • プロトタイプの列挙可能プロパティも拾う

結果:

newProperty: hacked が画面に堂々と登場する。


6. Property Injection が危険な理由

危険ポイント 内容
キー無検証 __proto__ をそのまま処理してしまう
プロトタイプ汚染 全オブジェクトが汚染される
EJSで表示 for...in がprototype由来プロパティを表示
他攻撃との連携 XSS と組み合わさるとアプリが崩壊

「Prototype Pollution は単体だと地味」
と言われることがありますが、
GUIに表示され、ロジックにも影響し、他の脆弱性と連鎖する
となれば話は別。破壊力は一気に跳ね上がります。


7. 防御方法

① 危険キーをブロックする

const blocked = ["__proto__", "constructor", "prototype"];
if (blocked.includes(key)) continue;

② マージ・クローン処理は自前実装しない

lodashでも古いバージョンは脆弱だったため常に最新版を使用すること

③ 文字列入力を JSON.parse しない

アルバム名は文字列で十分。
仕様を変えられるならここが最大の防御ポイント。

④ テンプレートで hasOwnProperty を利用

if (!Object.prototype.hasOwnProperty.call(friend, key)) continue;

おわりに

Prototype Pollution は地味に見えて、
実際は「アプリケーションの土台(プロトタイプ)に穴を開ける攻撃」。

小さなマージ関数ひとつが、
大規模なセキュリティ事故の始まりになることもあります。

コードレビューで見逃されやすい分、
「知っているかどうか」だけで攻撃と防御の差が大きく出る分野です。

0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?