Regex Dos in Django
How Regex Dos Manifests in Django
Regex DoS (Denial of Service) attacks in Django exploit the fact that certain regular expressions can take exponential time to process when given malicious input. This vulnerability, also known as ReDoS (Regular Expression Denial of Service), occurs when an attacker crafts input that forces the regex engine into catastrophic backtracking.
In Django applications, regex DoS commonly appears in several critical areas:
- URL routing: Django's URL resolver uses regex patterns to match incoming requests. A malicious URL can cause the router to consume excessive CPU time.
- Model field validation: Custom validators using regex can be exploited through form submissions or API payloads.
- Template rendering: Django templates support regex filters that can be abused with crafted input.
- Middleware processing: Request/response middleware that applies regex transformations can become a bottleneck.
The classic attack pattern involves creating input that matches the regex in multiple ways, forcing the engine to explore all possible match combinations. For example, consider this vulnerable Django URL pattern:
from django.urls import path
urlpatterns = [
path('user/', views.user_detail), # Vulnerable to ReDoS
path('search/', views.search), # Also vulnerable
] An attacker could send a request like /user/a[b-a]*/b where the character class [b-a] is invalid but forces catastrophic backtracking in Django's URL resolver. The slug converter uses regex under the hood, making it susceptible to this attack.
Another common pattern is in model validation:
import re
from django.db import models
class UserProfile(models.Model):
email = models.EmailField()
vanity_url = models.CharField(max_length=100, validators=[
# Vulnerable: nested quantifiers can cause exponential time
RegexValidator(
regex=r'^[a-z0-9]+(-[a-z0-9]+)*$',
message='Invalid URL format'
)
])The pattern (-[a-z0-9]+)* contains nested quantifiers that can be exploited with carefully crafted input containing many alternations.
Django-Specific Detection
Detecting regex DoS vulnerabilities in Django requires both static code analysis and dynamic testing. Here are Django-specific detection methods:
Static Analysis: Review your codebase for regex patterns in these Django-specific locations:
# Check URL patterns for vulnerable regex
from django.urls import URLPattern
def is_vulnerable_regex(pattern: str) -> bool:
# Look for nested quantifiers, alternation-heavy patterns
nested_quantifiers = r'([*+?]|(?:match|replace|search)).*b(?:match|replace|search)'
return bool(re.search(nested_quantifiers, pattern))
# Scan URL patterns
for pattern in urlpatterns:
if isinstance(pattern, URLPattern):
regex_pattern = pattern.pattern.regex.pattern
if is_vulnerable_regex(regex_pattern):
print(f'Vulnerable URL pattern: {regex_pattern}')Dynamic Testing with middleBrick: middleBrick's black-box scanning approach is particularly effective for detecting runtime regex DoS vulnerabilities in Django applications. The scanner tests your API endpoints with specially crafted payloads designed to trigger catastrophic backtracking.
middleBrick's regex DoS detection includes:
- Testing URL resolvers with pathological patterns
- Submitting form data with crafted strings to trigger model validators
- Analyzing template rendering paths for regex filter abuse
- Monitoring response times to detect abnormal processing delays
The scanner provides a security risk score (A–F) and specific findings with severity levels. For example, a vulnerable URL pattern might be flagged as:
Finding: Catastrophic backtracking in URL routing
Severity: High
Location: /user/<slug:id>
Impact: Attacker can cause 100% CPU usage with crafted URLs
Remediation: Replace nested quantifiers with atomic groupsPerformance Monitoring: Set up monitoring to detect regex DoS attacks in production:
from django.middleware import MiddlewareMixin
import time
class RegexMonitoringMiddleware(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
start_time = time.time()
response = view_func(request, *view_args, **view_kwargs)
elapsed = time.time() - start_time
if elapsed > 1.0: # Threshold in seconds
# Log potential regex DoS attempt
logger.warning(
f'Potential regex DoS detected: {request.path} '
f'took {elapsed:.2f}s'
)
return responseDjango-Specific Remediation
Remediating regex DoS vulnerabilities in Django requires both immediate fixes and architectural changes. Here are Django-specific approaches:
1. Use Atomic Groups for Vulnerable Patterns: Atomic groups prevent backtracking by committing to a match once it's found:
from django import forms
from django.core.validators import RegexValidator
# Vulnerable pattern (nested quantifiers)
vulnerable = r'^[a-z0-9]+(-[a-z0-9]+)*$'
# Fixed with atomic groups
fixed = r'^(?>[a-z0-9]+)(?:-(?>[a-z0-9]+))*$'
class SafeForm(forms.Form):
vanity_url = forms.CharField(
max_length=100,
validators=[
RegexValidator(
regex=fixed,
message='Invalid URL format'
)
]
)2. Replace Complex Regex with Simpler Validation: For URL routing, use Django's built-in converters when possible:
# Instead of vulnerable slug with complex regex
urlpatterns = [
path('user/<slug:id>', views.user_detail), # Potentially vulnerable
# Use path converter for simple alphanumeric IDs
path('user/<str:id>', views.user_detail_safe), # Safer
]3. Implement Input Length Limits: Add maximum length constraints to prevent exponential blowup:
from django import forms
class BoundedForm(forms.Form):
# Limit input to 50 characters max
vanity_url = forms.CharField(
max_length=50,
validators=[
RegexValidator(
regex=r'^[a-z0-9]+(-[a-z0-9]+)*$', # Still vulnerable but bounded
message='Invalid URL format'
)
]
)
def clean_vanity_url(self):
data = self.cleaned_data['vanity_url']
if len(data) > 50:
raise forms.ValidationError('Input too long')
return data4. Use Django's Built-in Validation: Leverage Django's native validators instead of custom regex:
from django.core.validators import EmailValidator
from django import forms
class SafeEmailForm(forms.Form):
email = forms.EmailField(
validators=[EmailValidator()], # Django's optimized validator
max_length=254 # Standard max email length
)
# Avoid custom regex for email validation
# email = forms.CharField(validators=[
# RegexValidator(regex=r'^...$', message='Invalid email')
# ])5. Implement Rate Limiting: Use Django middleware to prevent repeated attacks:
from django.middleware import MiddlewareMixin
from django.core.cache import cache
import time
class RateLimitingMiddleware(MiddlewareMixin):
def process_request(self, request):
key = f'request_count:{request.META.get("REMOTE_ADDR")}'
count = cache.get(key, 0)
if count > 10: # Max 10 requests per period
return HttpResponse('Rate limit exceeded', status=429)
cache.set(key, count + 1, timeout=60) # 1-minute window
return None6. Use Safe Libraries: For complex validation, use libraries designed to prevent ReDoS:
# Instead of vulnerable regex
import re
# Use safe alternatives
from django.core.validators import validate_slug
from django.utils.html import strip_tags
def safe_validation(value):
# Validate without regex
if not validate_slug(value):
raise ValidationError('Invalid format')
# Or use length-based validation
if len(value) > 100:
raise ValidationError('Input too long')
return TrueRelated CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |