Denial Of Service in Django
How Denial Of Service Manifests in Django
Denial of Service (DoS) attacks in Django applications exploit the framework's request handling and database interaction patterns. Unlike generic web applications, Django's synchronous request processing model creates specific vulnerabilities that attackers can leverage.
The most common Django-specific DoS vector is database query exhaustion. Consider a view that fetches related objects without proper limits:
def user_profile(request, user_id):
user = User.objects.get(id=user_id)
posts = Post.objects.filter(author=user)
comments = Comment.objects.filter(post__in=posts)
return render(request, 'profile.html', {
'user': user,
'posts': posts,
'comments': comments
})
If a user has thousands of posts and comments, this single request can trigger hundreds of database queries. An attacker can exploit this by repeatedly requesting user profiles, causing the database to become overwhelmed.
Another Django-specific pattern involves template rendering with complex loops. Django templates can execute arbitrary Python code through custom template tags or filters, potentially leading to CPU exhaustion:
{% for item in items %}
{% for subitem in item.subitems %}
{% for detail in subitem.details %}
{{ detail.content|custom_filter }}
{% endfor %}
{% endfor %}
{% endfor %}
Without limits on iteration depth or item counts, attackers can craft requests that cause exponential template rendering time.
File upload handling in Django also presents DoS opportunities. The default FileField and ImageField don't enforce size limits, allowing attackers to upload massive files that consume disk space and memory during processing:
class Document(models.Model):
file = models.FileField(upload_to='documents/')
# No size validation
Additionally, Django's middleware stack processes every request sequentially. A malicious request that triggers expensive middleware operations (like authentication against a slow external service) can block the entire worker thread, preventing other requests from being processed.
Django-Specific Detection
Detecting DoS vulnerabilities in Django requires examining both code patterns and runtime behavior. middleBrick's Django-specific scanning identifies several critical indicators.
For database-related DoS, middleBrick analyzes ORM queries to detect N+1 query patterns and missing limits:
def vulnerable_view(request, product_id):
product = Product.objects.get(id=product_id)
reviews = Review.objects.filter(product=product)
comments = Comment.objects.filter(review__in=reviews)
# No limits - potential DoS
return JsonResponse({
'product': product.name,
'reviews': [r.text for r in reviews],
'comments': [c.text for c in comments]
})
middleBrick flags this pattern and suggests adding limits:
reviews = Review.objects.filter(product=product)[:100]
comments = Comment.objects.filter(review__in=reviews)[:500]
The scanner also examines template rendering patterns, identifying nested loops without depth limits or item count restrictions. It specifically looks for custom template tags that might execute expensive operations.
For file upload vulnerabilities, middleBrick checks for missing validation in FileField and ImageField declarations, as well as view-level file handling:
def upload_view(request):
if request.method == 'POST':
form = UploadForm(request.POST, request.FILES)
if form.is_valid():
# No size validation
form.save()
return redirect('success')
return render(request, 'upload.html')
The scanner also tests for rate limiting gaps by sending rapid requests to identify endpoints that lack throttling mechanisms. Django-specific patterns include missing @ratelimit decorators or absent middleware configuration.
middleBrick's active scanning tests these vulnerabilities by simulating high-load scenarios and measuring response times, helping identify endpoints that degrade under pressure.
Django-Specific Remediation
Securing Django applications against DoS attacks requires combining Django's built-in features with defensive coding patterns. The Django framework provides several tools specifically designed for these scenarios.
For database query optimization, Django's select_related and prefetch_related methods prevent N+1 query problems:
def optimized_view(request, user_id):
user = User.objects.select_related('profile').get(id=user_id)
posts = Post.objects.select_related('author').prefetch_related('comments').filter(author=user)[:50]
return render(request, 'profile.html', {
'user': user,
'posts': posts
})
This reduces database queries from potentially hundreds to just a few, dramatically improving performance under load.
Django's Paginator class provides built-in protection against large result sets:
from django.core.paginator import Paginator
def paginated_view(request):
items = Item.objects.all()
paginator = Paginator(items, 25) # 25 items per page
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'items.html', {'page_obj': page_obj})
For template rendering, Django allows setting limits on template complexity:
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [BASE_DIR / 'templates'],
'OPTIONS': {
'libraries': {
'custom_tags': 'myapp.templatetags.custom_tags',
},
'builtins': ['myapp.templatetags.builtins'],
'libraries': {
'custom_tags': 'myapp.templatetags.custom_tags',
},
},
},
]
Additionally, implement custom template tags with execution limits:
@register.simple_tag(takes_context=True)
def safe_custom_tag(context, max_iterations=100):
if context['iterations'] > max_iterations:
raise TemplateSyntaxError('Maximum iterations exceeded')
# Safe implementation
For file upload protection, Django's FileField supports validators:
from django.core.validators import FileExtensionValidator, MaxValueValidator
def validate_file_size(value):
limit = 2.5 * 1024 * 1024 # 2.5 MB
if value.size > limit:
raise ValidationError(f'File too large (max {limit} bytes)')
class Document(models.Model):
file = models.FileField(
upload_to='documents/',
validators=[
validate_file_size,
FileExtensionValidator(allowed_extensions=['pdf', 'docx'])
]
)
Rate limiting in Django can be implemented using django-ratelimit or custom middleware:
from ratelimit.decorators import ratelimit
@ratelimit(key='ip', rate='10/m', method=['GET', 'POST'])
def protected_view(request):
# This view is rate limited to 10 requests per minute
return JsonResponse({'status': 'ok'})
For production deployments, combine these code-level protections with Django's caching framework to reduce database load:
from django.core.cache import cache
@cache_page(60 * 15) # Cache for 15 minutes
def cached_view(request):
# Expensive operation cached
return render(request, 'cached.html')
These Django-specific mitigations work together to create a robust defense against DoS attacks while maintaining application functionality.
Related CWEs: resourceConsumption
| CWE ID | Name | Severity |
|---|---|---|
| CWE-400 | Uncontrolled Resource Consumption | HIGH |
| CWE-770 | Allocation of Resources Without Limits | MEDIUM |
| CWE-799 | Improper Control of Interaction Frequency | MEDIUM |
| CWE-835 | Infinite Loop | HIGH |
| CWE-1050 | Excessive Platform Resource Consumption | MEDIUM |