Django API Security

Django Security Posture — What Django Gets Right and Wrong by Default

Django provides strong security defaults that many frameworks lack. It includes built-in protections against common web vulnerabilities: CSRF tokens are automatically included in forms, clickjacking is prevented via X-Frame-Options headers, and SQL injection is mitigated through its ORM. The framework ships with secure session handling and password hashing using PBKDF2 by default.

However, Django's API security posture is more nuanced. While Django REST Framework (DRF) offers authentication and permission classes, these are not enabled by default. A Django API endpoint without authentication is essentially an unauthenticated attack surface. Django's middleware stack doesn't automatically protect against BOLA (Broken Object Level Authorization) or IDOR (Insecure Direct Object Reference) attacks — these require explicit permission checks in your view logic.

The framework's flexibility becomes a liability when developers rely on defaults. Many Django APIs expose admin interfaces, debug endpoints, or verbose error messages in production. Django's settings.py file often contains hardcoded secrets or debug=True flags that remain in production code. The framework's ORM, while protecting against SQL injection, can still be vulnerable to NoSQL injection if developers use raw() queries or allow unsafe query parameters.

middleBrick's API security scanner specifically tests Django applications for these framework-specific vulnerabilities. When you scan a Django API endpoint, middleBrick checks for exposed Django admin panels, debug mode status, weak authentication implementations, and permission bypass attempts that are common in Django applications.

Top 5 Security Pitfalls in Django — Real Misconfigurations Developers Make

1. Debug Mode in Production: The most critical mistake is leaving DEBUG = True in production settings. This exposes stack traces with full file paths, database queries, and even secret keys to anyone who triggers an error. Django's debug mode also serves static files and templates directly, potentially exposing source code. Always set DEBUG = False and use a proper error handling page in production.

2. Missing Authentication on API Endpoints: Django REST Framework doesn't enforce authentication by default. Many developers create API views without setting authentication_classes or permission_classes, leaving endpoints completely open. A Django API without authentication is a free-for-all for attackers to enumerate data, modify records, or extract sensitive information.

3. Insecure Direct Object References: Django's generic views and serializers make it easy to expose object IDs directly in URLs. Without proper permission checks, an authenticated user can access any object by simply changing the ID parameter. This is BOLA — Broken Object Level Authorization — and it's one of the most common API vulnerabilities. Always validate that users have permission to access specific objects.

4. Exposed Django Admin Interface: The Django admin panel is a powerful tool but also a major attack surface if left exposed. Many developers forget to secure the admin interface with proper authentication, rate limiting, or IP restrictions. Admin interfaces often contain sensitive data and configuration options that should never be publicly accessible.

5. Weak Secret Management: Hardcoding secrets in settings.py or leaving default Django keys in production code is a critical vulnerability. Django's SECRET_KEY should be treated like any other application secret — it should never be committed to version control or exposed in error messages. Use environment variables or secret management services for production secrets.

Security Hardening Checklist — Actionable Config/Code Changes

Configuration Hardening:

# settings.py - Production configuration
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'
ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# Security middleware must be first
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Security settings
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_SECONDS = 31536000  # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_REFERRER_POLICY = 'same-origin'

# Rate limiting for login attempts
LOGIN_FAILURE_LIMIT = 5
LOGIN_FAILURE_TIMEOUT = 300  # 5 minutes

DRF Security Configuration:

# settings.py - DRF configuration
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/hour',
        'user': '1000/hour',
    },
    'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
    'PAGE_SIZE': 25,
}

View-Level Security:

# views.py - Secure API view example
from rest_framework import permissions, status
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.exceptions import NotFound, PermissionDenied

class SecureObjectView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    
    def get_object(self, pk):
        try:
            obj = MyModel.objects.get(pk=pk)
            # Check if user has permission to access this object
            if not self.request.user.has_permission_to_object(obj):
                raise PermissionDenied(detail='Access denied')
            return obj
        except MyModel.DoesNotExist:
            raise NotFound(detail='Object not found')
    
    def get(self, request, pk):
        obj = self.get_object(pk)
        serializer = MyModelSerializer(obj)
        return Response(serializer.data)
    
    def put(self, request, pk):
        obj = self.get_object(pk)
        # Validate that user can modify this object
        if not self.request.user.can_modify_object(obj):
            raise PermissionDenied(detail='Modification not allowed')
        serializer = MyModelSerializer(obj, data=request.data)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data)

middleBrick's scanner can verify these configurations are properly implemented. The scanner tests authentication enforcement, permission checks, rate limiting effectiveness, and exposed endpoints. You can run middleBrick scans as part of your CI/CD pipeline to ensure security configurations aren't accidentally broken during development.

Frequently Asked Questions

How does middleBrick scan Django APIs without credentials?
middleBrick performs black-box security scanning by testing the unauthenticated attack surface of your Django API endpoints. The scanner sends HTTP requests to your API endpoints and analyzes responses for security vulnerabilities like missing authentication, exposed debug information, and permission bypass attempts. No agents, no configuration files, and no credentials are required — just the API URL.
Can middleBrick detect BOLA (Broken Object Level Authorization) in Django?
Yes, middleBrick specifically tests for BOLA vulnerabilities common in Django applications. The scanner attempts to access objects using different user contexts and ID manipulation to verify that permission checks are properly enforced. It checks whether authenticated users can access objects they shouldn't have permission to view or modify, which is a critical security flaw in many Django APIs.
Does middleBrick work with Django REST Framework?
Absolutely. middleBrick is designed to scan any API endpoint, including those built with Django REST Framework. The scanner analyzes DRF-specific configurations like authentication classes, permission settings, and serializer implementations. It tests whether your DRF API endpoints properly enforce authentication, rate limiting, and object-level permissions as configured in your settings.py file.