ReDoS can be mitigated without changes to the regular expression engine, simply by setting a time limit for the execution of regular expressions when untrusted input is involved. ReDoS can be avoided entirely by using a non-vulnerable regular expression implementation. After
CloudFlare's
web application firewall (WAF) was brought down by a PCRE ReDoS in 2019, the company rewrote its WAF to use the non-backtracking Rust regex library, using an algorithm similar to
RE2. Vulnerable regular expressions can be detected programmatically by a
linter. Methods range from pure
static analysis to
fuzzing. In most cases, the problematic regular expressions can be rewritten as "non-evil" patterns. For example, (.*a)+ can be rewritten to ([^a]*a)+.
Possessive matching and atomic grouping, which disable backtracking for parts of the expression, can also be used to "pacify" vulnerable parts.
Linear-time (finite automata) regex While some regex libraries do not have built-in defence against ReDoS attacks, such as
C++ Standard Library ,
C POSIX library or
Boost boost.regex (which use backtracking, leading to exponential time), other regex libraries are engineered for preventing regex denial of service attacks. This is done using deterministic finite automata, which run in linear time relative to the input size. Using the
RE2 library by Google for
C++: import ; import std; using std::string; using re2::RE2; int main(int argc, char* argv[]) { string text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!" string pattern = "(a+)+$"; bool match = RE2::FullMatch(text, pattern); std::println("Match result: {}", match); } Using the regex crate for
Rust: use regex::Regex; fn main() { // Regex::new() returns Result and must be unwrapped match Regex::new(r"^(a+)+$") { Ok(re) => { let matches: bool = re.is_match("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"); println!("Match result: {}", matches); } Err(err) => { eprintln!("Failed to unwrap regex: {}", err); } } }
Regex match timeout Timeouts can be implemented to cancel regex tasks if they take too long. package org.wikipedia.examples; import java.util.concurrent.*; import java.util.regex.*; public class Example { public static boolean matchesWithTimeout(String regex, String input, long timeoutMillis) { ExecutorService executor = Executors.newSingleThreadExecutor(); Future future = executor.submit(() -> { Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); return matcher.matches(); }); try { return future.get(timeoutMillis, TimeUnit.MILLISECONDS); } catch (TimeoutException e) { System.err.printf("Regex evaluation timed out: %s%n", e.getMessage()); return false; } catch (InterruptedException | ExecutionException e) { System.err.printf("Interruption or execution: %s%n", e.getMessage()); e.printStackTrace(); return false; } finally { future.cancel(true); // Stop the thread executor.shutdownNow(); } } public static void main(String[] args) { String regex = "(a+)+$"; String input = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"; boolean result = matchesWithTimeout(regex, input, 100); // 100 ms timeout System.out.printf("Match result: %s%n", result); } } Timeouts are built in to the
.NET standard library, as the class System.Text.RegularExpressions.Regex supports a property MatchTimeout. The following is an example in
C#: namespace Wikipedia.Examples; using System; using System.Text.RegularExpressions; public class Example { static void Main(string[] args) { string pattern = @"(a+)+$"; string input = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaX"; try { Regex re = new(pattern, RegexOptions.None, TimeSpan.FromMilliseconds(100)); bool match = re.IsMatch(input); Console.WriteLine($"Match result: {match}"); } catch (RegexMatchTimeoutException ex) { Console.WriteLine($"Regex operation timed out! {ex.Message}"); } } } ==See also==