HP Fortify检测到正则表达式DoS漏洞,求可行的验证方案
Great question—this is such a common pain point when balancing configurability and security. HP Fortify’s warning is spot-on: untrusted regex (even from config files, since they can be modified by admins or compromised) can trigger catastrophic backtracking, hogging CPU and causing a DoS. Let’s walk through actionable, practical solutions to fix this without ditching dynamic configuration.
Why the Risk Happens
First, a quick recap: regex patterns with nested quantifiers (like (a+)+b) or unbounded backtracking points can get stuck processing certain input strings, spinning the CPU indefinitely. This is the core of regex-based DoS attacks.
Solution 1: Validate Regex for Dangerous Patterns + Length Limits
Before compiling the regex from your .properties file, run a pre-check to block high-risk constructs and enforce reasonable length limits. This acts as a first line of defense.
Here’s a Java example of a validation method:
import java.util.regex.Pattern; import java.util.regex.Matcher; public class RegexSecurityValidator { // Regex to detect common dangerous regex constructs: nested quantifiers, unbounded groups, etc. private static final Pattern DANGEROUS_CONSTRUCTS = Pattern.compile( ".*(\\([^)]*\\+\\)|\\+\\([^)]*\\)|\\*\\([^)]*\\)|\\{\\d+,\\}\\([^)]*\\)|" + "\\([^)]*\\)\\{\\d+,\\}|\\([^)]*\\)\\+|\\([^)]*\\)\\*|\\?=.*\\(|\\?<=.*\\()" ); private static final int MAX_REGEX_LENGTH = 200; // Adjust based on your needs public static boolean isRegexSafe(String regex) { if (regex == null || regex.isBlank()) { return false; } // Block overly long regex (reduces attack surface) if (regex.length() > MAX_REGEX_LENGTH) { return false; } // Block patterns with high-risk backtracking potential Matcher dangerousMatcher = DANGEROUS_CONSTRUCTS.matcher(regex); return !dangerousMatcher.matches(); } }
Use this before compiling:
String regexFromConfig = properties.getProperty("app.validation.regex"); if (!RegexSecurityValidator.isRegexSafe(regexFromConfig)) { throw new IllegalArgumentException("Invalid or unsafe regular expression in config"); } Pattern safePattern = Pattern.compile(regexFromConfig);
Solution 2: Enforce Timeouts for Compilation and Matching
Even with validation, edge cases can slip through. Java 9+ natively supports timeouts for regex operations, which prevent threads from getting stuck indefinitely. For older Java versions, use an executor service to wrap regex operations with time limits.
Java 9+ Example
import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; public class TimedRegex { public static void processWithRegex(String regex, String input) throws TimeoutException { // Set 1-second timeout for compilation Pattern pattern = Pattern.compile(regex, Pattern.DOTALL, 1, TimeUnit.SECONDS); // Set 1-second timeout for matching Matcher matcher = pattern.matcher(input); if (matcher.find(1, TimeUnit.SECONDS)) { // Process match result } } }
Java 8 and Below Example
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.regex.Pattern; public class TimedRegexJava8 { public static Pattern compileWithTimeout(String regex, long timeoutSeconds) throws TimeoutException { ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Pattern> future = executor.submit(() -> Pattern.compile(regex)); try { return future.get(timeoutSeconds, TimeUnit.SECONDS); } catch (Exception e) { future.cancel(true); // Interrupt the stuck thread throw new TimeoutException("Regex compilation timed out"); } finally { executor.shutdown(); } } }
Solution 3: Use a Safe Regex Engine
Swap Java’s default regex engine with a linear-time engine that avoids catastrophic backtracking entirely. Google’s re2j library is a great option—it’s designed to handle untrusted regex safely, with no risk of infinite loops.
Example with re2j
First, add the dependency (Maven):
<dependency> <groupId>com.google.re2j</groupId> <artifactId>re2j</artifactId> <version>1.7</version> </dependency>
Then use it in your code:
import com.google.re2j.Pattern; import com.google.re2j.Matcher; public class SafeRegexEngine { public static boolean validateInput(String regexFromConfig, String input) { Pattern safePattern = Pattern.compile(regexFromConfig); Matcher matcher = safePattern.matcher(input); return matcher.matches(); } }
Note: re2j doesn’t support all Java regex features (like lookbehind assertions or backreferences in some cases), so test your existing regex patterns for compatibility first.
Solution 4: Regex Whitelisting (Most Secure Option)
If you don’t need fully arbitrary regex, use a whitelist of pre-vetted, safe regex patterns. Let your config file reference keys instead of raw regex, then map those keys to hardcoded, tested patterns.
Example
In your .properties file:
app.validation.regex.key=USER_ID_VALIDATOR
In your Java code:
import java.util.Map; import java.util.regex.Pattern; public class RegexWhitelist { private static final Map<String, Pattern> SAFE_REGEX_MAP = Map.of( "USER_ID_VALIDATOR", Pattern.compile("^[a-zA-Z0-9]{8,16}$"), "EMAIL_VALIDATOR", Pattern.compile("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"), "PHONE_VALIDATOR", Pattern.compile("^\\+?[0-9\\s-]{10,15}$") ); public static Pattern getPatternFromConfig(String configKey) { Pattern pattern = SAFE_REGEX_MAP.get(configKey); if (pattern == null) { throw new IllegalArgumentException("Unsupported regex key in config"); } return pattern; } }
This gives you configurability (you can switch which regex is used via config) while eliminating all regex DoS risk.
Which Solution Should You Choose?
- Full custom regex needed: Combine validation (Solution 1) + timeouts (Solution 2) + re2j (Solution 3) for layered defense.
- Limited regex use cases: Go with whitelisting (Solution 4)—it’s the most secure and low-maintenance option.
内容的提问来源于stack exchange,提问作者Vicky

