Aikido

遅い正規表現からのガード:ReDoS攻撃の防止

読みやすさ

ルール
ガード 遅れを防ぐ 遅い 正規表現 正規表現
正規 正規表現  ネストされた 量化子 または 
あいまいな パターン  引き起こす 壊滅的な 
バックトラック  パフォーマンスの問題 の問題を引き起こします。
対応言語 45+

はじめに

正規表現は適切な入力があれば、アプリケーションを数秒から数分間フリーズさせることができる。正規表現エンジンがパターンにマッチしようとして指数関数的に増加するパスを探索すると、破滅的なバックトラックが発生する。以下のような正規表現は (イ+ロ 攻撃者は、正規表現サービス拒否(ReDoS)攻撃によってこれを悪用し、リクエストのタイムアウトが発生するかプロセスがクラッシュするまで、正規表現エンジンに100%のCPUを消費させるように細工した入力を送信する。

なぜそれが重要なのか

セキュリティへの影響(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_\\-\\.]+)+ 指数関数的なバックトラックが発生する。次のようなメールの場合 ああああああああああああああああ!正規表現エンジンは、失敗する前に無数の組み合わせを試す。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$/の場合、"aaaaaaaaaaaaaaaaaaa!"(aが30個以上、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.

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

RE2(Googleが使用)はバックトラックを完全に禁止することで、線形時間の実行を保証している。すべての機能(後方参照、ルックアラウンド)をサポートしているわけではないが、ReDoSを完全に防ぐことができる。クリティカルなセキュリティチェックには、RE2バインディングや同様のエンジンを使うことを検討してください。JavaScriptの場合は、ビルトインの代替手段がないので、パターン設計とタイムアウトが主な防御となります。

すべての正規表現操作にタイムアウトを設けるべきか?

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

既存の脆弱な正規表現パターンを修正するには?

まず、正規表現が必要かどうかを判断しましょう。検証作業の多くは、includes() や startsWith()、 split() などの文字列メソッドを使用したほうが簡単です。正規表現が必要な場合は、パターンを平坦化することで入れ子になった量化子を取り除きます。(a+)+をa+に置き換える。アトミック・グループや所有量化子も、正規表現エンジンがサポートしていれば使用する。複雑なパターンの場合は、より単純な正規表現または文字列操作で入力を複数回パースすることを検討する。

まずは無料で体験

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

クレジットカードは不要。