【入門編】プロトタイプ汚染と変数のスコープ:グローバル変数が引き起こすセキュリティリスクの防御策 – JavaScriptコア文法・モダン言語仕様と変数・関数解析バイブル

こんにちは!フロントエンドからNode.jsの深部まで、JavaScriptの生態系を知り尽くしたチーフアーキテクトの私です。

今回は、JavaScriptの学習において誰もが一度は通り過ぎるけれど、実はプロの現場でも命取りになる極めて危険な罠——「グローバル変数」と「プロトタイプ汚染」の関係について、防衛的プログラミングの視点から徹底的に紐解いていきます。

「変数なんて `var` や `let` で適当に宣言しておけば動くでしょ?」なんて思っていませんか?
ここをクリアすれば、あなたの書くコードは一気にプロダクションクオリティになりますよ。一緒に本質をマスターしていきましょう!

—

1. そもそも「グローバル変数」って何がそんなに悪いの?

JavaScriptのプログラムが実行されるとき、すべての変数は何らかの「スコープ(変数の有効範囲)」の中に属しています。

中でも、どの関数やブロックの包囲網にも属さず、一番外側の世界(ブラウザなら `window` オブジェクト、Node.jsなら `global` オブジェクト)にプカプカと浮かんでいる変数をグローバル変数と呼びます。

どこからでも触れる=どこからでも壊される

グローバル変数は、アプリケーションのどこからでも読み書きできるという「一見すると便利な手抜き」ができます。しかし、これは裏を返せば、「悪意あるコードや、無知なサードパーティ製ライブラリが、いつでも勝手に書き換えられる」という致命的なセキュリティリスクを抱え込んでいることと同義です。

// 【危険なコードの例】
// 意図せずグローバル汚染を引き起こしている状態
var currentUser = “Alice”;

function doSomething() {
// うっかり var や let を付け忘れた!
// これにより、厳格モード(strict mode)外ではグローバル空間の currentUser が書き換わります。
currentUser = “Hacker”;
}

doSomething();
console.log(currentUser); // “Hacker” に書き換わってしまった!

この「うっかり変数の宣言を忘れる」挙動や、不用意にグローバル空間にオブジェクトをばらまく設計が、やがてプロトタイプ汚染(Prototype Pollution)という巨大なセキュリティホールへと直結していくのです。

—

2. プロトタイプ汚染のメカニズムを解剖する

JavaScriptは、クラスベースではなくプロトタイプベースのオブジェクト指向言語です。すべてのオブジェクトは、親にあたる `Object.prototype` からメソッドやプロパティを継承(プロトタイプチェーン)しています。

ここで、グローバルスコープで管理されているデータや、外部から入力されたJSONデータなどを経由して、この根本である `Object.prototype` が書き換えられてしまったらどうなるでしょうか?

イメージ図で考えてみましょう。

[ 悪意ある入力 / 脆弱なコード ]
↓
Object.prototype を直接いじってしまう!
↓
┌────────────────────────────────────────┐
│ Object.prototype │
│ ├─ toString() │
│ ├─ … │
│ └─ isAdmin: true ← 【ここに汚染が発生!】 │
└────────────────────────────────────────┘
▲
│ すべてのオブジェクトがこれを継承する
│
[ ユーザーが作った普通のオブジェクト ]
const user = {};
console.log(user.isAdmin); // なぜか true になってしまう!?

そう、`Object.prototype` が汚染されると、アプリケーション内のすべてのオブジェクトがその影響を受け、予期せぬ挙動や権限昇格(セキュリティバイパス)を引き起こすのです。

コードで見るプロトタイプ汚染の恐ろしさ

以下のコードを見てください。グローバルに変数を管理する緩い設計や、安全性の低いオブジェクトの結合処理が、いかに危険か分かります。

// 【危険なオブジェクトの結合(マージ)処理の例】
function unsafeMerge(target, source) {
for (let key in source) {
// 脆弱な実装:キーのバリデーションをせず、そのまま代入している
target[key] = source[key];
}
return target;
}

// 攻撃者から送られてきたと仮定する悪意あるJSONデータ
// __proto__ を経由して Object.prototype をハックしようとする
const maliciousJSON = ‘{“__proto__”: {“isAdmin”: true}}’;
const payload = JSON.parse(maliciousJSON);

// 空のオブジェクトをマージしてみる
const userConfig = {};
unsafeMerge(userConfig, payload);

// さあ、全く関係ない別のオブジェクトを作ってみましょう
const guestUser = { name: “Bob” };

// 驚愕の結果:何も設定していないのに、isAdmin が true になっている!
console.log(guestUser.isAdmin); // true (プロトタイプ汚染の完成)

この脆弱性突かれると、アクセス権限チェックをすり抜けて管理画面に入れてしまったり、予期せぬプロパティの存在によってサーバーがクラッシュしたりします。これが「グローバルな文脈や全域に影響を及ぼす汚染」の正体です。

—

3. 防衛的プログラミング:どうやってこのリスクを防ぐのか?

では、私たちはこの恐怖から身を守るために、どうコードを書くべきでしょうか?
答えは明確です。「スコープを極限まで閉じ込め、グローバル変数を排除し、オブジェクトの安全性を担保する」ことです。

対策1: `let` と `const`、そしてブロックスコープの徹底

もう `var` を使う理由は現代のJavaScriptにはありません。変数は必ず `const`(再代入不可)、変更が必要な場合のみ `let` を使い、影響範囲(スコープ)を波括弧 `{}` の内側に閉じ込めましょう。

// 【安全なコードの例】
function processUserData() {
// スコープがこの関数内に限定されるため、外部から汚染されない
const currentUser = “Alice”;

// 処理…
console.log(currentUser);
}

// グローバル空間は常にクリーンに保つ

対策2: プロトタイプ汚染を防ぐマージ関数の実装

もしオブジェクトを結合(マージ)する処理を書く場合は、`__proto__` や `constructor`、`prototype` といった危険なプロパティ名がキーに含まれていないかを必ずブラックリスト方式やホワイトリスト方式で弾く必要があります。

// 【防衛的なオブジェクトのマージ関数】
function safeMerge(target, source) {
for (let key in source) {
// プロトタイプ汚染を狙った危険なキーの侵入を防ぐガード条項
if (key === ‘__proto__’ || key === ‘constructor’ || key === ‘prototype’) {
continue; // 危険なキーはスキップする
}

// 自前のプロパティであるかも確認する (hasOwnProperty)
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
return target;
}

const safeConfig = {};
const maliciousPayload = JSON.parse(‘{“__proto__”: {“isAdmin”: true}}’);

safeMerge(safeConfig, maliciousPayload);

const anotherUser = { name: “Charlie” };
console.log(anotherUser.isAdmin); // undefined (汚染を防げました!)

さらに近代的なJavaScript環境やNode.jsであれば、`Object.freeze(Object.prototype)` を使ってプロトタイプ自体をイミュータブル(変更不可能な状態)にしてしまうという強固な防御策をとることもあります。

—

まとめ:クリーンなスコープがアプリの安全性を守る

今回は、グローバル変数の安易な使用がなぜプロトタイプ汚染に繋がり、いかにセキュリティリスクを孕んでいるのかを解説しました。

1. グローバル変数はどこからでも書き換えられるため、予測不可能なバグや脆弱性の温床になる。
2. 不適切なオブジェクト操作やJSONパースは、すべてのオブジェクトの親である `Object.prototype` を汚染(Prototype Pollution)させる。
3. `let` / `const` によるブロックスコープの徹底と、危険なキー(`__proto__` 等)を弾く防衛的コードでリスクを完全にシャットアウトする。

JavaScriptの挙動の裏側にあるメモリ空間やスコープの仕組みを意識できるようになると、あなたの書くコードの信頼性は劇的に向上します。ここをクリアできれば、もう基本はバッチリマスターできていますよ!

自信を持って、安全で美しいコードを書き進めていきましょう。

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