非効率な正規表現によるReDoS

過剰なバックトラッキングによる正規表現DoS

説明

(.+)+、(.*)+、(r+){m,} のような入れ子の反復や曖昧な選択肢では、バックトラッキングが急増し、入力長に対して指数的な時間がかかる場合があります。攻撃者が多数の a の後に X を置くなど、一致しそうで一致しない長い入力を送ると、CPUを占有して応答を遅らせたり停止させたりできます。Node.jsのようなイベントループ型の実行環境では、1件のリクエストがサービス全体に影響することがあります。

実際の計算量はパターン、入力、エンジンによって異なります。入れ子の反復があっても、すべての照合が指数時間になるわけではありません。

想定される影響

  • 長時間の照合により、リクエスト処理が遅延・停止するおそれがあります。
  • バックトラッキングでCPU、メモリ、スタックを過剰に消費する可能性があります。
  • APIの応答が遅れ、タイムアウトが増えるおそれがあります。
  • 少数の悪意あるリクエストで、インスタンス全体の処理能力が低下する可能性があります。

対処方法

  • 入れ子や重複する反復を除去してください。必要な入力に合う場合は、例えば /^_(__|.)+_$/ を /^_(__|[^_])+_$/ に変更できます。
  • 可能なら固定長や {0,64} などの上限を使ってください。
  • 照合前に入力長を制限し、例えば256文字を超える値は拒否してください。
  • 必要な機能に適合する場合は、RE2系ライブラリなど、線形時間で照合するエンジンを検討してください。
  • リンターや静的解析は補助として使い、実際の入力制限とエンジンの動作も確認してください。
  • リクエストのタイムアウトでは同期的な照合を中断できません。終了できるワーカーやプロセス、または制限を強制できるエンジンを使ってください。

例

変更前

javascript
// 入れ子の反復でバックトラッキングが急増するおそれ
const express = require('express');
const app = express();

// 英字グループをさらに反復: ^([a-z]+)+$
const usernameRegex = new RegExp("^([a-z]+)+$", "i");

app.get('/check', (req, res) => {
  const name = (req.query.name || '').toString();
  // 長さ制限がなく、危険な正規表現を使用
  if (usernameRegex.test(name)) {
    return res.send('ok');
  }
  res.status(400).send('invalid');
});

app.listen(3000);
// 'a'.repeat(50000) + '!' のような長い入力でCPUを占有するおそれ

変更後

javascript
// 制限した正規表現と入力長。RE2は任意の代替手段
const express = require('express');
// 任意: RE2を使う場合
// const RE2 = require('re2');
const app = express();

// 入れ子を除去して上限を明示: ^[A-Za-z]{1,64}$
// 標準のRegExpを使用
const usernameRegex = new RegExp("^[A-Za-z]{1,64}$");
// RE2を使う場合:
// const usernameRegex = new RE2('^[A-Za-z]{1,64}$');

app.get('/check', (req, res) => {
  const name = String(req.query.name || '');

  // 追加の防御として照合前に長さを制限
  if (name.length === 0 || name.length > 64) {
    return res.status(400).send('invalid');
  }

  if (!usernameRegex.test(name)) {
    return res.status(400).send('invalid');
  }
  res.send('ok');
});

app.listen(3000);

解説:

  • 変更前: ^([a-z]+)+$ はグループの内外で反復します。aaaa...! のような長い入力でバックトラッキングが増え、Node.jsのイベントループを止める可能性があります。長さの制限もありません。
  • 変更後: 入れ子の反復を除去し、長さの上限を明示します。RE2系エンジンはバックトラッキングを避けるための選択肢です。これらの変更により、細工した入力がCPUを占有してサービスを遅延させる機会を減らします。

参考資料