ReDoS攻撃

ウィキペディアから、無料の百科事典

ReDoS攻撃(ReDos attack)もしくは、正規表現DoS攻撃(せいきひょうげんドスこうげき)とは、評価に時間のかかる正規表現(regex)や入力を与えることでDoS攻撃(サービス拒否攻撃)を引き起こすアルゴリズム的計算量攻撃である[1]。この攻撃は、多くの正規表現の実装が超線形的な最悪計算量を持つという事実を悪用している[2]。特定の正規表現と入力の組み合わせにおいて、実行時間は入力サイズに対して多項式時間または指数関数時間で増加する。したがって、攻撃者は特別に細工した正規表現や入力を提供することで、プログラムに膨大な時間を費やさせることができる。その結果、プログラムは遅延するか応答不能になる[3][4]。

概要

正規表現のマッチングは、有限オートマトンを構築することで実行できる。正規表現は非決定性有限オートマトン(NFA)へ簡単に変換できる。NFAでは、各状態と入力記号に対して、複数の次の状態が存在しうる。オートマトンを構築した後の処理には、いくつかの可能性が存在する。

  • エンジンがそれを決定性有限オートマトン(DFA)に変換し、その結果に入力を通す。
  • 一致が見つかるか、すべてのパスを試して失敗するまで、エンジンが可能なすべてのパスを1つずつ試す(バックトラッキング)[5][6]。
  • エンジンが非決定性オートマトン上のすべての可能なパスを並列に処理する。
  • エンジンが非決定性オートマトンをDFAに遅延評価で(つまり、マッチングの実行中にオンザフライで)変換する。

上記アルゴリズムのうち、最初の2つには問題がある。1つ目の方法は、決定性オートマトンが最大で(は非決定性オートマトンの状態数)の状態を持つ可能性があるため、NFAからDFAへの変換に指数関数時間がかかる可能性がある点で問題である。2つ目の方法は、非決定性オートマトンが長さの入力に対して指数関数的な数のパスを持つ可能性があるため、入力の探索にも指数関数時間がかかる点で問題である[7]。一方で、残りの2つのアルゴリズムはこのような病的な挙動を示さない。

病的な挙動を示さない正規表現の場合、問題のあるアルゴリズムは通常高速であり、実際には正規表現をの時間で「コンパイル」し、の時間でマッチングできると期待できる。その代わり、NFAのシミュレーションとDFAの遅延計算は、最悪計算量がとなる[注釈 1]。正規表現DoS攻撃は、これらの期待がユーザーから提供された正規表現に適用され、悪意のある正規表現がマッチャーの最悪計算量を引き起こす場合に発生する。

正規表現アルゴリズムは効率的に記述できるが、既存のほとんどの正規表現エンジンは、常に効率的に解決できるとは限らない追加の構文で正規表現言語を拡張している。このような拡張パターンは、ほとんどのプログラミング言語における正規表現の実装において、本質的にバックトラッキングの使用を強制している。

例

指数関数的なバックトラッキング

最も深刻な問題は、正規表現マッチングのバックトラッキングで発生し、一部のパターンでは実行時間が入力文字列の長さに対して指数関数的になる[8]。文字の文字列に対する実行時間はとなる。これは、正規表現が以下の3つの特性を持つ場合に発生する。

  • 正規表現が部分式に対して繰り返し(+、*)を適用している。
  • 部分式が同じ入力に対して複数の方法でマッチングできるか、部分式がより長い可能性のある一致の接頭辞である入力文字列にマッチングできる。
  • 繰り返される部分式の後に、その部分式がマッチングしないものにマッチングする式がある。

2つ目の条件は、以下の2つの例で説明するのがわかりやすい。

  • (a|a)+$では、繰り返しが部分式a|aに適用されており、これは選択のどちらの側でもaにマッチングできる。
  • (a+)*$では、繰り返しが部分式a+に適用されており、これはaやaaなどにマッチングできる。

これら両方の例では、文字列の末尾にマッチさせるために$を使用し、3つ目の条件を満たしたが、この目的のために別の文字を使用することも可能である。例えば、(a|aa)*cも同じ問題のある構造を持っている。

上記の3つの正規表現はすべて、という形式の文字列に適用された場合に指数関数的な実行時間を示す。例えば、バックトラッキング式の式エンジンでaaaaaaaaaaaaaaaaaaaaaaaaxとマッチングさせようとすると、完了までにかなりの時間がかかり、xの前のaが1つ増えるごとに実行時間は約2倍になる。

指数関数的ではなく、多項式時間のバックトラッキングが発生することも可能である。これも十分に入力が長ければ問題を引き起こす可能性があるが、悪意のある入力が大きな影響を与えるためにはさらに長くなる必要があるため、この問題への注目度は低い。このようなパターンの例としては、入力が任意の長さのaの連続である場合のa*b?a*cが挙げられる。

オンラインリポジトリの脆弱な正規表現

いわゆる「悪意のある」または脆弱な正規表現が、オンラインの正規表現リポジトリで見つかっている。全体の正規表現を攻撃するには、脆弱な部分式を1つ見つけるだけで十分であることに注意が必要である。

1. RegExLib、id=1757(電子メールの検証) - 下線の部分を参照。 ^([a-zA-Z0-9])(([\-.]|[_]+)?([a-zA-Z0-9]+))*(@){1}[a-z0-9]+[.]{1}(([a-z]{2,3})|([a-z]{2,3}[.]{1}[a-z]{2,3}))$ 2. OWASP Validation Regex Repository、Javaクラス名 - 下線の部分を参照。 ^(([a-z])+.)+[A-Z]([a-z])+$

これら2つの例は、aaaaaaaaaaaaaaaaaaaaaaaa!という入力に対しても影響を受けやすい。

攻撃手法

検索パターンをクライアントに提供させるWebサービスなど、正規表現自体がユーザー入力の影響を受ける場合、攻撃者は悪意のある正規表現を注入してサーバーのリソースを消費させることができる。したがって、ほとんどの場合、ユーザーがサーバー上で任意のパターンを実行できないようにすることで、正規表現DoS攻撃を回避できる。このケースでは、Webアプリケーションやデータベースが主に脆弱なアプリケーションとなる。あるいは、悪意のあるページがユーザーのWebブラウザをフリーズさせたり、任意の量のメモリを消費させたりする可能性もある。

しかし、脆弱な正規表現がすでにサーバー側に存在する場合、攻撃者は最悪の挙動を引き起こす入力を提供できる可能性がある。このケースでは、電子メールスキャナや侵入検知システムも脆弱になる可能性がある。

Webアプリケーションの場合、プログラマはシステムのクライアント側とサーバー側の両方で入力を検証するために同じ正規表現を使用することがある。攻撃者はクライアントのコードを検査して悪意のある正規表現を探し、サーバーをフリーズさせるために細工された入力を直接Webサーバーに送信することができる[9]。

緩和策

ReDoSは、信頼できない入力が関与する場合に正規表現の実行に時間制限(タイムアウト)を設定するだけで、正規表現エンジンを変更することなく緩和できる[10]。

脆弱性のない正規表現の実装を使用することで、ReDoSを完全に回避できる。2019年にCloudflareのWebアプリケーションファイアウォール(WAF)がPCREのReDoSによってダウンした後、同社はWAFを書き直し、RE2に似たアルゴリズムを使用するバックトラッキングを行わないRustの正規表現ライブラリを採用した[11][12]。

脆弱な正規表現は、Lintによってプログラム的に検出できる[13][14][15]。その手法は、純粋な静的解析[16][17]からファジング[18]まで多岐にわたる。ほとんどの場合、問題のある正規表現は「悪意のない」パターンに書き換えることができる。例えば、(.*a)+は([^a]*a)+に書き換えることができる。式の一部でバックトラッキングを無効にする所有格マッチング(Possessive matching)やアトミックグループ化(Atomic grouping)[19]も、脆弱な部分を無効化するために使用できる[20][21][22]。

線形時間(有限オートマトン)の実装

C++標準ライブラリの<regex>、C POSIXライブラリの<regex.h>[23]、Boostのboost.regexなど、一部の正規表現ライブラリはReDoS攻撃に対する防御機能を組み込んでいない(バックトラッキングを使用するため、指数関数時間につながる)が、他の正規表現ライブラリは正規表現DoS攻撃を防ぐように設計されている。これは決定性有限オートマトンを使用して行われ、入力サイズに対して線形時間で実行される。

GoogleによるC++用のRE2ライブラリを使用した場合[24]:

import <re2/re2.h>;
import std;

using std::string_view;
using re2::RE2;

constexpr string_view TEXT = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!";
constexpr string_view PATTERN = "^(a+)+$";

int main(int argc, char* argv[]) {
    bool isMatch = RE2::FullMatch(TEXT, PATTERN);
    std::println("Match result: {}", isMatch);
}

Rust用のregexクレートを使用した場合[25]:

use regex::Regex;

const TEXT: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!";
const PATTERN: &str = r"^(a+)+$";

fn main() {
    // Regex::new() returns Result<Regex, Error> and must be unwrapped
    match Regex::new(PATTERN) {
        Ok(re) => {
            let is_match: bool = re.is_match(TEXT);
            println!("Match result: {}", is_match);
        }
        Err(err) => {
            eprintln!("Failed to unwrap regex: {}", err);
        }
    }
}

正規表現マッチングのタイムアウト

タイムアウトを使用して、時間がかかりすぎる正規表現タスクをキャンセルすることができる。ただし、これを安全に行う唯一の方法は、正規表現自体がタイムアウトのしきい値を超えたかどうかをチェックすることである。そうでない場合、ストップトークン(協調的なスレッド割り込みを使用する)は正規表現によってブロックされ、孤立したFutureとPromiseやスレッドは単にリークし、OSのAPIを通じてスレッドを直接強制終了すると破損やデッドロックを引き起こす可能性がある。しかし、それを子プロセスとして分離し、しきい値を超えた場合にプロセスを終了させることは安全である。

タイムアウトは.NET標準ライブラリに組み込まれており、System.Text.RegularExpressions.RegexクラスはMatchTimeoutプロパティをサポートしている[26]。以下はC#での例である。

namespace Wikipedia.Examples;

using System;
using System.Text.RegularExpressions;

public class Example
{
    private const string Text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!";
    private const string Pattern = @"^(a+)+$";

    static void Main(string[] args)
    {
        try
        {
            Regex re = new(Pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100));
            bool isMatch = re.IsMatch(Text);
            Console.WriteLine($"Match result: {isMatch}");
        }
        catch (RegexMatchTimeoutException ex)
        {
            Console.WriteLine($"Regex operation timed out! {ex.Message}");
        }
    }
}

コンパイル時正規表現

コンパイル時正規表現とは、構文解析と検証をコンパイル時に移行し、実行時のオーバーヘッドを排除する正規表現のことである。ただし、コンパイル時正規表現は、コンパイル時にパターンを提供できる場合にのみ有用である。

Hana DusíkováによるC++のコンパイル時正規表現(ctre)ライブラリは、ctre::match<>関数オブジェクトを提供し、コンパイル時に正規表現の構文解析を実行する[27]。コンパイル時のパターンによってコンパイルがconstexpr評価のステップ制限を超えた場合、コンパイルに失敗する。この機能はC++標準ライブラリへの追加が提案されているが、現時点では含まれていない[28]。

import std;
import ctre;

using std::string_view;

constexpr string_view TEXT = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!";

int main(int argc, char* argv[]) {
    // ctre::match<> is a ctre::regex_results<> type
    // matching logic is done at compile-time when marked constexpr
    constexpr bool isMatch = ctre::match<"^(a+)+$">(TEXT);
    std::println("Match result: {}", isMatch);
}

Dプログラミング言語は、コンパイル時正規表現であるstd.regex.ctRegexを提供している[29]。

Rustでは、コンパイル時正規表現は、コンパイル時の正規表現検証のためのさまざまなマクロを提供するlazy_regex、ctreg、regex-automataなどのサードパーティ製クレートによって実現できる[30]。

C#には、System.Text.RegularExpressions.GeneratedRegexAttribute属性があり、これはpartialメソッドでのソース生成を通じて正規表現の実装を生成する[31]。

namespace Wikipedia.Examples;

using System;
using System.Text.RegularExpressions;

public partial class Example
{
    [GeneratedRegex(@"^(a+)+$")]
    private static partial Regex Pattern();

    private const string Text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!";

    static void Main(string[] args)
    {
        // IsMatch() logic happens at runtime
        bool isMatch = Pattern().IsMatch(Text);
        Console.WriteLine($"Match result: {isMatch}");
    }
}

脚注

注釈

  1. ↑ DFAの遅延計算は通常、決定性オートマトンと同等の速度に達しつつ、最悪の場合の動作をNFAのシミュレーションと同様に保つことができる。しかし、実装はかなり複雑であり、より多くのメモリを使用する可能性がある。

出典

  1. ↑ “Regex Denial of Service”. OWASP (2010年2月10日). 2026年10月27日閲覧。
  2. ↑ Davis, James; Louis, Michael; Coghlan, Christy; Servant, Francisco; Lee, Dongyoon (2019). “Why Aren't Regular Expressions a Lingua Franca? An Empirical Study on the Re-use and Portability of Regular Expressions”. The ACM Joint European Software Engineering Conference and Symposium on the Foundations of Software Engineering: 443-454 2026年10月27日閲覧。.
  3. ↑ “Security Bulletin: Caution Using Regular Expressions”. RiverStar Software (2010年1月18日). 2011年7月15日時点のオリジナルよりアーカイブ。2010年4月16日閲覧。
  4. ↑ Ristic, Ivan (2010-03-15). ModSecurity Handbook. London, UK: Feisty Duck Ltd. p. 173. ISBN 978-1-907117-02-2. オリジナルの2016-08-08時点におけるアーカイブ。 2010年4月16日閲覧。
  5. ↑ Crosby and Wallach, Usenix Security (2003年). “Regular Expression Denial Of Service”. 2005年3月1日時点のオリジナルよりアーカイブ。2010年1月13日閲覧。
  6. ↑ Bryan Sullivan (2010年5月3日). “Regular Expression Denial of Service Attacks and Defenses”. 2010年5月6日閲覧。
  7. ↑ Kirrage, J.; Rathnayake, A.; Thielecke, H. (2013). “Static Analysis for Regular Expression Denial-of-Service Attacks”. Network and System Security. Madrid, Spain: Springer. pp. 135–148. doi:10.1007/978-3-642-38631-2_11.
  8. ↑ Jim Manico and Adar Weidman (2009年12月7日). “OWASP Podcast 56 (ReDoS)”. 2010年4月2日閲覧。
  9. ↑ Barlas, Efe; Du, Xin; Davis, James (2022). “Exploiting Input Sanitization for Regex Denial of Service”. ACM/IEEE International Conference on Software Engineering: 1–14 2026年10月27日閲覧。.
  10. ↑ “Backtracking in .NET regular expressions - .NET” (英語). learn.microsoft.com (2023年8月11日). 2026年10月27日閲覧。
  11. ↑ “Making the WAF 40% faster” (英語). The Cloudflare Blog (2020年7月1日). 2026年10月27日閲覧。
  12. ↑ Cox, Russ (2007年). “Regular Expression Matching Can Be Simple And Fast”. 2011年4月20日閲覧。
  13. ↑ Schmidt, Michael (2023年3月30日). “RunDevelopment/scslre”. 2026年10月27日閲覧。
  14. ↑ TSUYUSATO, Kitsune. “recheck Introduction” (英語). 2026年10月27日閲覧。
  15. ↑ Davis, James. “vuln-regex-detector/src/detect/README.md” (英語). 2026年10月27日閲覧。
  16. ↑ Thielecke, H. (2013年). “Regular expression denial of service (ReDoS) static analysis”. 2014年8月3日時点のオリジナルよりアーカイブ。2013年5月30日閲覧。
  17. ↑ van der Merwe, B. (2017年). “Regex Static Analysis”. 2017年8月12日閲覧。
  18. ↑ “Fuzzing with Static Analysis” (英語). 2026年10月27日閲覧。
  19. ↑ “Essential classes: Regular Expressions: Quantifiers: Differences Among Greedy, Reluctant, and Possessive Quantifiers”. Oracle. 2020年10月7日時点のオリジナルよりアーカイブ。2016年12月23日閲覧。
  20. ↑ “compose-regexp.js, "Atomic matching"” (2024年1月2日). 2026年10月27日閲覧。
  21. ↑ “tc39/proposal-regexp-atomic-operators” (英語). Ecma TC39 (2023年12月31日). 2026年10月27日閲覧。
  22. ↑ “Preventing Regular Expression Denial of Service (ReDoS)”. 2026年10月27日閲覧。
  23. ↑ “regex(3) - Linux manual page” (2025年6月28日). 2026年10月27日閲覧。
  24. ↑ “Github - google/re2” (2025年7月1日). 2026年10月27日閲覧。
  25. ↑ “regex - Rust”. 2025年9月15日閲覧。
  26. ↑ “Regex.MatchTimeout Property (System.Text.RegularExpressions) Microsoft Learn”. 2025年9月15日閲覧。
  27. ↑ Hana Dusíková (2026年5月8日). “Compile Time Regular Expressions v3”. Hana Dusíková. 2026年10月27日閲覧。
  28. ↑ Hana Dusíková. “Compile Time Regular Expressions”. WG 21. 2026年7月9日閲覧。
  29. ↑ D Language (2026年7月9日). “std.regex”. D Language. 2026年10月27日閲覧。
  30. ↑ Canop (2026年2月11日). “Crate lazy_regex”. 2026年10月27日閲覧。
  31. ↑ “GeneratedRegexAttribute Class”. Microsoft Learn. 2026年7月9日閲覧。

関連項目

外部リンク