プロトタイプ汚染

プロトタイプ汚染

説明

外部入力でオブジェクトを選び、そのプロパティに書き込むと、__proto__ などのキーで共有プロトタイプにアクセスできる場合があります。汚染されたプロトタイプを継承するオブジェクトから想定外のプロパティが読まれ、アプリケーションがその値をどう使うかによって、セキュリティ上の問題につながるおそれがあります。

想定される影響

  • 汚染されたプロパティを使う機能の想定外の動作
  • データ処理や設定の変更によるデータ破損
  • 汚染された値に依存する認証・認可の判断の回避

対処方法

  • 最後のフィールド名だけでなく、途中のオブジェクトを選ぶキーも検証し、__proto__、constructor、prototype を拒否してください。
  • 変更可能なフィールドは、サーバーが管理する許可リストに限定してください。リクエストから渡された許可リストは信用しないでください。
  • Object.hasOwn で継承されたプロパティを区別し、辞書として使う保存先には Object.create(null) や Map を検討してください。

例

変更前

javascript
app.get('/test/:id', (req, res) => {
    let id = req.params.id;
    let items = req.session.todos[id];
    if (!items) {
        items = req.session.todos[id] = {};
    }
    // 変更前: 外部入力をそのままキーとして使います。
    items[req.query.name] = req.query.text;
    res.sendStatus(200);
});

変更後

javascript
app.get('/test/:id', (req, res) => {
    const id = req.params.id;
    const blockedKeys = new Set(['__proto__', 'constructor', 'prototype']);
    const allowedKeys = new Set(['title', 'text', 'done']);
    if (blockedKeys.has(id) || !allowedKeys.has(req.query.name)) {
        return res.status(400).send('Invalid key name');
    }
    if (!Object.hasOwn(req.session.todos, id)) {
        req.session.todos[id] = Object.create(null);
    }
    const items = req.session.todos[id];
    items[req.query.name] = req.query.text;
    res.sendStatus(200);
});

説明:

  • 変更前: id が __proto__ なら共有プロトタイプを選択でき、未検証のフィールド名で値を書き込みます。
  • 変更後: オブジェクトを選ぶキーと最後のフィールドを両方検証し、継承されたプロパティを既存の項目として扱いません。セッションの todos 保存先は初期化済みと仮定しています。

参考資料