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]

Reply via email to