「家の設計図を書き換えられる?」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];
}
}
}
—
最後に:セキュリティは「泥棒とのイタチごっこ」じゃない
セキュリティと聞くと、「攻撃者との終わりのない戦い」のように感じるかもしれません。でも、実際には「家の鍵をしっかりかける」「知らない人を家に入れない」といった、基本の積み重ねが一番の防御になります。
今回学んだ「プロトタイプ汚染」も、「外部からの入力は、たとえ便利そうなマージ関数であっても、決してそのまま信用してはいけない」という鉄則を守るだけで、ほとんどの脅威を退けることができます。
まずは、皆さんが普段書いているコードの中で、ユーザーの入力をそのままオブジェクトに代入している場所がないか、一度見直してみませんか? 一歩ずつ、安全な開発のスキルを身につけていきましょう!
コメント