説明
利用者が制御するオブジェクトの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を反復の上限として信頼します。変更後は実際の配列かを確認し、コピーや走査の前に上限超過を拒否します。例の数値変換とは別に、各要素の型と値の範囲を検証し、リクエストサイズを制限してください。