ループ上限の操作

利用者が指定するlength値による過剰な反復処理

説明

利用者が制御するオブジェクトのlengthをループの終了条件にそのまま使うと、1e12や1e100などの極端に大きな値を指定される可能性があります。長時間または事実上終わらない反復によりCPUやメモリーを消費し、サービス拒否を引き起こすおそれがあります。例えば、配列を想定するコードへ{ length: 1000000000000 }を渡すと、想定を大きく超える反復が発生します。

想定される影響

  • 過剰な反復でリクエスト処理が遅延し、サーバーが応答しなくなる可能性があります。
  • 長時間のループがCPUを占有し、ほかのリクエストに影響するおそれがあります。
  • ループ内のpushやconcatなどでメモリーが増え、プロセスが停止する可能性があります。
  • イベントループの停止で応答時間が延び、ロードバランサーやゲートウェイでタイムアウトが発生するおそれがあります。

対処方法

  • 長さを読む前にArray.isArrayで実際の配列か確認してください。
  • アプリケーションで固定の上限MAX_ITEMSを設け、コピーや走査の前に超過する配列を拒否してください。
  • 配列に似たオブジェクトを許可する場合は、長さが有限の非負整数であることを確認し、確保や変換の前にMAX_ITEMSを適用してください。
  • 入力制限にArray.from(obj).slice(0, MAX_ITEMS)を使わないでください。切り出す前の変換でコレクション全体が処理されます。
  • リクエストサイズを制限し、処理の目的に合わせて各要素を検証してください。

例

変更前

javascript
const express = require("express");
const app = express();
app.use(express.json());

// 信頼できないlengthを反復の上限に直接使用
app.post("/sum", (req, res) => {
  const list = req.body.items; // リクエスト元が自由に指定できる
  let total = 0;

  // BAD: { items: { length: 1e12 } }で過剰な反復が発生
  for (let i = 0; i < list.length; i++) {
    total += Number(list[i] || 0);
  }

  res.json({ total });
});

app.listen(3000);

変更後

javascript
const express = require("express");
const app = express();
app.use(express.json());

const MAX_ITEMS = 10000; // 処理の要件に合う上限を設定

// 型と長さを検証し、制限した配列だけを走査
app.post("/sum", (req, res) => {
  const list = req.body.items;

  // 1) 実際の配列だけを許可
  if (!Array.isArray(list)) {
    return res.status(400).json({ error: "items must be an array" });
  }

  // 2) 走査やコピーの前に長さの上限を適用
  if (list.length > MAX_ITEMS) {
    return res.status(400).json({ error: "too many items" });
  }

  let total = 0;
  for (const v of list) {
    total += Number(v || 0);
  }

  res.json({ total });
});

app.listen(3000);

変更前はリクエスト本文のitems.lengthを反復の上限として信頼します。変更後は実際の配列かを確認し、コピーや走査の前に上限超過を拒否します。例の数値変換とは別に、各要素の型と値の範囲を検証し、リクエストサイズを制限してください。

参考資料