https://bz.apache.org/bugzilla/show_bug.cgi?id=69823
--- Comment #2 from kaka123pwn <[email protected]> --- ```import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.concurrent.TimeUnit; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeoutException; public class CompressionHandler { private Pattern noCompressionUserAgents; public CompressionHandler() { this.noCompressionUserAgents = null; } // VULNERABLE METHOD - Original implementation public void setNoCompressionUserAgents(String noCompressionUserAgents) { this.noCompressionUserAgents = noCompressionUserAgents == null || noCompressionUserAgents.length() == 0 ? null : Pattern.compile(noCompressionUserAgents); // VULNERABLE: ReDoS } public boolean shouldCompress(String userAgent) { if (noCompressionUserAgents == null || userAgent == null) { return true; } return !noCompressionUserAgents.matcher(userAgent).find(); } // Test with various ReDoS patterns public static void testMultipleReDoSPatterns() { System.out.println("=== Testing Multiple ReDoS Patterns ==="); String[][] testCases = { {"(a+)+$", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}, {"(a|a)+$", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}, {"(a*)*$", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}, {"(a+)*$", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}, {"^(a+)+", "!aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"}, {"(\\w+)+$", "word1word2word3word4word5word6word7!"}, {"(a|aa)+$", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"}, {"(a|a?)+$", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"} }; CompressionHandler handler = new CompressionHandler(); for (int i = 0; i < testCases.length; i++) { String pattern = testCases[i][0]; String input = testCases[i][1]; System.out.println("\nTest " + (i + 1) + ":"); System.out.println("Pattern: " + pattern); System.out.println("Input: " + input); handler.setNoCompressionUserAgents(pattern); long startTime = System.nanoTime(); try { boolean result = handler.shouldCompress(input); long endTime = System.nanoTime(); double durationMs = (endTime - startTime) / 1_000_000.0; System.out.println("Result: " + result + " | Time: " + durationMs + "ms"); } catch (Exception e) { long endTime = System.nanoTime(); double durationMs = (endTime - startTime) / 1_000_000.0; System.out.println("Exception after " + durationMs + "ms: " + e.getMessage()); } } } // Test exponential growth with increasing input sizes public static void testExponentialGrowth() { System.out.println("\n=== Testing Exponential Time Growth ==="); CompressionHandler handler = new CompressionHandler(); handler.setNoCompressionUserAgents("(a+)+$"); int[] sizes = {10, 15, 20, 25, 30, 35}; for (int size : sizes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < size; i++) { sb.append('a'); } sb.append('!'); // The character that causes backtracking String input = sb.toString(); System.out.println("\nInput length: " + input.length()); long startTime = System.nanoTime(); try { boolean result = handler.shouldCompress(input); long endTime = System.nanoTime(); double durationMs = (endTime - startTime) / 1_000_000.0; System.out.println("Time: " + durationMs + "ms | Result: " + result); } catch (Exception e) { long endTime = System.nanoTime(); double durationMs = (endTime - startTime) / 1_000_000.0; System.out.println("Exception after " + durationMs + "ms: " + e.getMessage()); } } } // Test with timeout protection public static void testWithTimeout() { System.out.println("\n=== Testing With Timeout Protection ==="); CompressionHandler handler = new CompressionHandler(); handler.setNoCompressionUserAgents("(a+)+$"); String maliciousInput = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa!"; ExecutorService executor = Executors.newSingleThreadExecutor(); Future<Boolean> future = executor.submit(() -> { return handler.shouldCompress(maliciousInput); }); try { Boolean result = future.get(5, TimeUnit.SECONDS); // 5 second timeout System.out.println("Result: " + result); } catch (TimeoutException e) { future.cancel(true); System.out.println("Operation timed out after 5 seconds - ReDoS detected!"); } catch (Exception e) { System.out.println("Exception: " + e.getMessage()); } finally { executor.shutdown(); } } // Test realistic user agent patterns public static void testRealisticScenarios() { System.out.println("\n=== Testing Realistic User Agent Patterns ==="); CompressionHandler handler = new CompressionHandler(); // Safe patterns String[] safePatterns = { "Googlebot", "Bingbot|Slurp|DuckDuckBot", "MSIE [6-8]", "Android.*AppleWebKit" }; String testUA = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"; for (String pattern : safePatterns) { handler.setNoCompressionUserAgents(pattern); long startTime = System.nanoTime(); boolean result = handler.shouldCompress(testUA); long endTime = System.nanoTime(); double durationMs = (endTime - startTime) / 1_000_000.0; System.out.println("Pattern: " + pattern + " | Time: " + durationMs + "ms | Result: " + result); } } public static void main(String[] args) { // Run all tests testMultipleReDoSPatterns(); testExponentialGrowth(); testWithTimeout(); testRealisticScenarios(); // Additional long-running test System.out.println("\n=== Long Running Test (30+ seconds expected) ==="); testVeryLongInput(); } // Test with very long input that should take a very long time public static void testVeryLongInput() { CompressionHandler handler = new CompressionHandler(); handler.setNoCompressionUserAgents("(a+)+$"); // Create a very long input (this will take a VERY long time) StringBuilder sb = new StringBuilder(); for (int i = 0; i < 40; i++) { // Warning: This will take minutes to hours! sb.append('a'); } sb.append('!'); String veryLongInput = sb.toString(); System.out.println("Testing with input length: " + veryLongInput.length()); System.out.println("WARNING: This may take a VERY long time or hang!"); System.out.println("Press Ctrl+C to stop if it takes too long..."); long startTime = System.currentTimeMillis(); try { boolean result = handler.shouldCompress(veryLongInput); long endTime = System.currentTimeMillis(); System.out.println("Completed in " + (endTime - startTime) + "ms | Result: " + result); } catch (Exception e) { long endTime = System.currentTimeMillis(); System.out.println("Exception after " + (endTime - startTime) + "ms: " + e.getMessage()); } } } // Safe implementation with validation class SafeCompressionHandler { private Pattern noCompressionUserAgents; private static final long REGEX_TIMEOUT_MS = 1000; // 1 second timeout public SafeCompressionHandler() { this.noCompressionUserAgents = null; } public void setNoCompressionUserAgents(String noCompressionUserAgents) { if (noCompressionUserAgents == null || noCompressionUserAgents.length() == 0) { this.noCompressionUserAgents = null; return; } // Validate for dangerous patterns if (isDangerousPattern(noCompressionUserAgents)) { throw new SecurityException("Rejected potentially dangerous regex pattern: " + noCompressionUserAgents); } this.noCompressionUserAgents = Pattern.compile(noCompressionUserAgents); } private boolean isDangerousPattern(String pattern) { // Check for nested quantifiers and exponential patterns return pattern.matches(".*\\([^)]*[+*][^)]*[+*].*") || // Nested quantifiers pattern.matches(".*\\([^)]*\\|[^)]*\\).*[+*].*") || // Alternation with quantifiers pattern.matches(".*\\(.*\\).*\\1.*") || // Backreferences pattern.contains("((") || // Nested groups pattern.matches(".*\\+\\+.*|.*\\*\\*.*"); // Multiple consecutive quantifiers } public boolean shouldCompress(String userAgent) { if (noCompressionUserAgents == null || userAgent == null) { return true; } // Use timeout protection (simulated for Java 8) long startTime = System.currentTimeMillis(); Matcher matcher = noCompressionUserAgents.matcher(userAgent); while (!matcher.find()) { if (System.currentTimeMillis() - startTime > REGEX_TIMEOUT_MS) { throw new RuntimeException("Regex matching timed out after " + REGEX_TIMEOUT_MS + "ms"); } } return !matcher.find(); } } ``` can you test with this -- You are receiving this mail because: You are the assignee for the bug. --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
