Aikido

ReDoS攻撃を防ぐために、低速な正規表現に注意すべき理由

可読性

ルール
低速な 正規表現に 対する 防御。
ネストされた 量指定子や 
曖昧な パターンを 持つ 正規表現は、 壊滅的な 
バックトラッキングや パフォーマンスの 問題を引き起こす 可能性があります。
サポート言語: 45+

はじめに

正規表現は、適切な入力によってアプリケーションを数秒から数分間フリーズさせる可能性があります。壊滅的なバックトラッキングは、正規表現エンジンがパターンを一致させようとする際に、指数関数的に増加するパスを探索するときに発生します。次のような正規表現は、 (a+)+b 有効な入力に一致させるにはマイクロ秒かかりますが、末尾にbがないaの文字列を拒否するには何時間もかかることがあります。攻撃者は、正規表現サービス拒否(ReDoS)攻撃を通じてこれをエクスプロイトし、リクエストタイムアウトが発生するかプロセスがクラッシュするまで、正規表現エンジンがCPUを100%消費するような細工された入力を送信します。

なぜ重要なのか

セキュリティ上の影響(ReDoS攻撃): 攻撃者は、巧妙に作成された入力を含む単一のリクエストでアプリケーションを麻痺させることができます。メール検証やURL解析パターンが一般的な標的です。帯域幅を必要とする従来のDoS攻撃とは異なり、ReDoSはごくわずかなペイロードしか必要としません。

パフォーマンスの低下: 通常のユーザー入力が壊滅的なバックトラッキングを引き起こし、応答時間がミリ秒から秒単位に急増する可能性があります。これは特定の入力パターンでのみ現れるため、デバッグが困難な予測不能なレイテンシーを引き起こします。

本番環境でのインシデント: 脆弱な正規表現は、Node.jsのイベントループをブロックしたり、スレッドプールリソースを消費したりします。リクエストが蓄積されるにつれてメモリが増加し、システムが応答しなくなります。マイクロサービスでは、1つの脆弱な正規表現が依存サービスに障害を連鎖させます。

検出の困難さ: 短い入力でテスト中に問題なく機能するパターンは、長い入力では指数関数的に遅くなります。脆弱性はしばしば本番環境まで見過ごされ、アクティブなインシデント中に緊急デプロイが必要となります。

コード例

❌ 非準拠:

function validateEmail(email) {
    const regex = /^([a-zA-Z0-9_\-\.]+)+@([a-zA-Z0-9_\-\.]+)+\.([a-zA-Z]{2,5})$/;
    return regex.test(email);
}

function extractURLs(text) {
    const regex = /(https?:\/\/)?([\w\-])+\.(\w+)+([\w\-\.,@?^=%&:/~\+#]*)+/g;
    return text.match(regex);
}

なぜそれが安全でないのか: ネストされた量指定子 ([a-zA-Z0-9_\\-\\.]+)+ 指数バックトラッキングを作成します。例えば、次のようなメールの場合: aaaaaaaaaaaaaaaaaaaaaaaaa!、正規表現エンジンは失敗するまでに無数の組み合わせを試行します。URL正規表現には複数のネストされた量指定子があり、問題が複雑化するため、期待される構造を持たない有効な文字の長い文字列のような入力で簡単にエクスプロイト可能になります。

✅ 準拠済み:

function validateEmail(email) {
    const regex = /^[a-zA-Z0-9_\-\.]+@[a-zA-Z0-9_\-\.]+\.[a-zA-Z]{2,5}$/;
    return regex.test(email);
}

function extractURLs(text) {
    const regex = /https?:\/\/[\w\-]+\.[\w\-]+(?:[\w\-\.,@?^=%&:/~\+#]*)?/g;
    return text.match(regex);
}

安全な理由: ネストされた量指定子を削除することで、壊滅的なバックトラックがなくなります。[a-zA-Z0-9_\-\.]+のような単一の量指定子は線形時間で実行されます。URLパターンは、ネストされた繰り返しではなく、オプションのサフィックス (?:...)? を持つ非キャプチャグループを使用しており、入力の長さや内容に関わらず予測可能なパフォーマンスを保証します。

まとめ

正規表現のパフォーマンスは、最適化だけでなくセキュリティ上の懸念事項です。ネストされた量指定子、繰り返しグループ内の重複する文字クラス、および曖昧な代替パターンについて、すべての正規表現パターンを確認してください。デプロイ前に壊滅的なバックトラッキングを特定するために、病的な入力(有効な文字の長い文字列の後に無効な末尾が続くもの)で正規表現パターンをテストしてください。可能な場合は、複雑な正規表現を、予測可能なパフォーマンス特性を持つ文字列解析関数に置き換えてください。

よくある質問

ご質問がありますか?

壊滅的なバックトラックを引き起こすパターンは何ですか?

一般的な原因としては、(a+)+、(a*)*、(a+)*bのようなネストされた量指定子、(a|a)*や(a|ab)*のような重複するパターンを持つ選択、(a?)+のようなオプションコンポーネントを持つ繰り返しなどが挙げられます。正規表現エンジンが同じ部分文字列を複数の方法でマッチできるパターンは、指数関数的な探索空間を生み出します。それ自体が量指定されているグループ内の量指定子(+、*、{n,m})に注意してください。

自身の正規表現がReDoSに対して脆弱であるかを、どのようにテストしますか?

regex101.comのようなオンラインツールを使用してください。これらのツールは実行ステップを表示し、壊滅的なバックトラックについて警告します。有効な文字の長い文字列の後にバックトラックを強制する文字が続くテスト入力を作成してください。パターン /^(a+)+b$/ の場合、「aaaaaaaaaaaaaaa!」(30個以上のa、bなし)でテストしてください。実行に数ミリ秒以上かかる場合、その正規表現は脆弱です。多層防御として、本番環境の正規表現操作にタイムアウトを実装してください。

壊滅的なバックトラッキングと線形バックトラッキングの違いは何ですか?

リニアバックトラッキングは、正規表現が選択肢を順次試行するものの、以前の選択肢を再評価しない場合に発生します。処理は入力サイズに比例して増加します。カタストロフィックバックトラッキングは、ネストされた量指定子がエンジンに指数関数的に多くの組み合わせを試行させる場合に発生します。入力長nの場合、実行時間はO(2^n)またはそれ以上になる可能性があります。これは、控えめな入力サイズであっても、ミリ秒と数分の違いを生じさせます。

先読みと後読みを安全に使用できますか?

Lookaheads (?=...) and lookbehinds (?<=...) themselves don't cause catastrophic backtracking, but they can hide vulnerable patterns. A lookahead containing (a+)+ is still vulnerable. Use lookarounds for their intended purpose (assertions without consuming characters), not as a workaround for complex matching. Keep the patterns inside lookarounds simple and test them thoroughly.

壊滅的なバックトラッキングを防ぐ正規表現エンジンはありますか?

Googleが使用するRE2は、バックトラッキングを完全に禁止することで、線形時間での実行を保証します。全ての機能(バックリファレンス、ルックアラウンドなど)をサポートしているわけではありませんが、ReDoSを完全に防ぎます。重要なセキュリティチェックには、RE2バインディングまたは類似のエンジンを使用することを検討してください。JavaScriptには組み込みの代替手段がないため、パターン設計とタイムアウトが主要な防御策となります。

すべての正規表現操作にタイムアウトを追加すべきですか?

信頼できない入力(ユーザー提供データ、外部API応答)の場合、はい。予想される複雑さに応じて、100~500ミリ秒のような妥当なタイムアウトを設定してください。Node.jsでは、regex.test()を直接タイムアウトさせることはできませんが、まず入力長を検証するか、タイムアウト付きのワーカー スレッドで正規表現を実行することができます。正規表現マッチングを試みる前に、妥当な長さ制限を超える入力を拒否してください。

既存の脆弱な正規表現パターンをどのように修正しますか?

まず、正規表現が本当に必要かどうかを判断してください。多くの検証タスクは、includes()、startsWith()、split()のような文字列メソッドでよりシンプルに処理できます。正規表現が必要な場合は、パターンをフラット化してネストされた量指定子を排除します。(a+)+ を a+ に置き換えます。正規表現エンジンがサポートしている場合は、アトミックグループまたは所有格量指定子を使用します。複雑なパターンには、よりシンプルな正規表現または文字列操作で入力を複数回に分けて解析することを検討してください。

今すぐ、安全な環境へ。

コード、クラウド、ランタイムを1つの中央システムでセキュアに。
脆弱性を迅速に発見し、自動的に修正。

クレジットカードは不要です | スキャン結果は32秒で表示されます。