【入門編】 JavaScriptプロトタイプ汚染(Prototype Pollution)の仕組みと悪用 – オフェンシブセキュリティ & リバースエンジニアリング防御ガイド

「家の設計図を書き換えられる?」JavaScriptのプロトタイプ汚染って何だろう?

こんにちは!セキュリティの世界へようこそ。今日は、Web開発で避けて通れないけれど、意外と見落とされがちな「プロトタイプ汚染(Prototype Pollution)」というちょっと怖い攻撃について、一緒に紐解いていきましょう。

いきなり難しい言葉が出てきましたが、大丈夫です。まずは身近な例えから入っていきましょう。

1. 「プロトタイプ」って何?:家の「共通設計図」

JavaScriptには「プロトタイプ」という仕組みがあります。これは、すべてのオブジェクト(データのかたまり)が参照する「共通の設計図」のようなものです。

想像してみてください。あなたは今、たくさんの「家」を建てる仕事をしています。すべての家には、最初から「ドア」や「窓」という部品がついていますよね。もし、これらを1軒ずつ作っていたら大変です。だから、設計図に「すべての家にはドアがある」と書いておけば、新しい家を作るたびに、その設計図をコピーして「ドア付きの家」がすぐに完成します。

この「共通の設計図(Object.prototype)」に、もし泥棒が勝手に書き込みをして、「すべての家の玄関の鍵は、誰でも開けられるようにする」なんてメモを付け加えたらどうなるでしょう? そう、すべての家が危険にさらされてしまいますよね。これが「プロトタイプ汚染」の正体です。

—

2. どうやって汚染されるの?:マージ関数の落とし穴

攻撃者が狙うのは、データを結合(マージ)する機能です。例えば、ユーザーから送られてきた設定情報を、システムのデフォルト設定と混ぜ合わせる処理があるとします。

// 悪い例:ユーザーの入力をそのままマージしてしまう処理
function merge(target, source) {
  for (let key in source) {
    // ここでチェックがないと、危険な「__proto__」が混入する!
    if (typeof source[key] === 'object') {
      merge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
}

// 攻撃データ(JSON形式のつもり)
const maliciousData = JSON.parse('{"__proto__": {"isAdmin": true}}');
merge({}, maliciousData);

上記のコードでは、__proto__ という特殊なキーを使って、JavaScriptの共通設計図(Object.prototype)に isAdmin: true という設定をねじ込んでいます。すると、システム内のすべてのオブジェクトが「自分は管理者(isAdmin)である」と勘違いし始めてしまうのです。

—

3. 一歩ずつ対策を学んでいきましょう!

攻撃の仕組みがわかれば、守り方も見えてきます。今日から実践できる対策を2つ紹介しますね。

対策その1:設計図を「凍結」する(Object.freeze)

一番確実なのは、共通の設計図を「これ以上書き換えられないように固める」ことです。これを Object.freeze() と呼びます。

// プログラムの初期段階で設計図を凍結する
Object.freeze(Object.prototype);

// これ以降、誰かが __proto__ を書き換えようとしてもエラーになるか無視されます
const obj = {};
obj.__proto__.isAdmin = true; // 効かない!
console.log(obj.isAdmin);    // undefined になるので安心

対策その2:入力値をチェックする

ユーザーから送られてくるデータの中に __proto__ や constructor といった危険なキーワードが含まれていないか、処理の入り口でチェックする習慣をつけましょう。

// マージ前に危険なキーを弾く関数
function isSafeKey(key) {
  return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
}

function safeMerge(target, source) {
  for (let key in source) {
    if (isSafeKey(key)) {
      // 安全なキーだけを処理する
      target[key] = source[key];
    }
  }
}

—

最後に:セキュリティは「泥棒とのイタチごっこ」じゃない

セキュリティと聞くと、「攻撃者との終わりのない戦い」のように感じるかもしれません。でも、実際には「家の鍵をしっかりかける」「知らない人を家に入れない」といった、基本の積み重ねが一番の防御になります。

今回学んだ「プロトタイプ汚染」も、「外部からの入力は、たとえ便利そうなマージ関数であっても、決してそのまま信用してはいけない」という鉄則を守るだけで、ほとんどの脅威を退けることができます。

まずは、皆さんが普段書いているコードの中で、ユーザーの入力をそのままオブジェクトに代入している場所がないか、一度見直してみませんか? 一歩ずつ、安全な開発のスキルを身につけていきましょう!

コメント

タイトルとURLをコピーしました