Dns Rebinding in Django
How Dns Rebinding Manifests in Django
Dns Rebinding in Django applications often exploits the framework's trust in request origins and internal service discovery. The attack works by manipulating DNS TTL values to make a malicious domain resolve to a victim's internal IP address after initially resolving to an external IP. Django's default middleware stack and view patterns can inadvertently expose internal services to this attack.
A common manifestation occurs in Django's settings.py where developers configure internal API endpoints or service URLs. Consider this vulnerable pattern:
# settings.py - Vulnerable configuration
INTERNAL_API_URL = os.getenv('INTERNAL_API_URL', 'http://localhost:8000')An attacker registers a domain that resolves to their server initially, then uses a short TTL to rebind to the victim's internal network. When Django applications make requests to what they believe is an external API, they're actually hitting internal services.
Django's HttpRequest.META handling can also be exploited. The framework trusts certain headers for internal routing:
# views.py - Vulnerable internal service access
from django.http import JsonResponse
import requests
def internal_data_view(request):
# Attacker can manipulate X-Forwarded-For to appear as internal request
if request.META.get('HTTP_X_FORWARDED_FOR') == '127.0.0.1':
internal_data = requests.get('http://localhost:9000/internal-data')
return JsonResponse({'data': internal_data.json()})
return JsonResponse({'error': 'Unauthorized'}, status=403)Another Django-specific vector involves the ORM's connection pooling when connecting to internal databases. If connection strings are constructed from request parameters:
# urls.py - Vulnerable URL pattern
urlpatterns = [
path('db-query/<str:db_host>/<str:query>/', db_query_view),
]
# views.py - Vulnerable database access
import psycopg2
from django.http import JsonResponse
def db_query_view(request, db_host, query):
conn = psycopg2.connect(
host=db_host, # DNS rebinding target
database='app_data'
)
cur = conn.cursor()
cur.execute(query)
results = cur.fetchall()
return JsonResponse({'results': results})This pattern allows attackers to specify internal database hosts that get resolved through manipulated DNS, potentially exposing internal database services.
Django-Specific Detection
Detecting DNS rebinding in Django requires examining both configuration files and runtime behavior. Start by auditing settings.py for hardcoded internal service URLs and environment variables that accept external input:
# Check for vulnerable patterns in settings.py
grep -r 'localhost\|127\.0\.0\.1\|0\.0\.0\.0' .
grep -r 'INTERNAL_API_URL\|INTERNAL_SERVICE' .
# Look for unsafe request header usage
grep -r 'HTTP_X_FORWARDED_FOR\|HTTP_X_REAL_IP' .middleBrick's Django-specific scanning identifies these patterns automatically. The scanner tests for DNS rebinding by:
- Analyzing OpenAPI specs for internal service endpoints
- Checking middleware configurations for unsafe header processing
- Testing response behaviors when internal services are accessed
- Verifying that no internal services are exposed through public endpoints
Run middleBrick to detect Django-specific DNS rebinding vulnerabilities:
# Scan a Django application
middlebrick scan https://your-django-app.com
# Check for specific findings related to internal service access
middlebrick report --category "SSRF" --format jsonThe scanner's LLM security module also checks for AI-specific rebinding attacks where model endpoints might be exposed to internal network access. This is particularly relevant for Django applications using ML libraries or AI integrations.
Manual testing involves setting up a test domain with short TTL values and monitoring Django's behavior when the domain resolves to internal IPs. Use tools like dnsrebind.sh or custom DNS servers to simulate the attack.
Django-Specific Remediation
Remediating DNS rebinding in Django requires a defense-in-depth approach. Start with configuration hardening:
# settings.py - Secure configuration
import socket
from urllib.parse import urlparse
def validate_internal_url(url):
"""Validate that URLs don't point to internal networks"""
parsed = urlparse(url)
if not parsed.scheme or not parsed.netloc:
raise ValueError('Invalid URL format')
# Check for private IP ranges
try:
ip = socket.gethostbyname(parsed.netloc)
octets = list(map(int, ip.split('.')))
# Private IP ranges: 10.x, 172.16-31.x, 192.168.x, 127.x
if octets[0] == 10 or \
(octets[0] == 172 and 16 <= octets[1] <= 31) or \
(octets[0] == 192 and octets[1] == 168) or \
octets[0] == 127:
raise ValueError('Internal network address detected')
except socket.gaierror:
# DNS resolution failed, treat as potentially malicious
raise ValueError('DNS resolution failed')
return url
# Use validated URLs
INTERNAL_API_URL = validate_internal_url(
os.getenv('INTERNAL_API_URL', 'https://trusted-external-service.com')
)For request handling, implement strict header validation and use Django's built-in security middleware:
# middleware.py - DNS rebinding protection
from django.middleware.security import SecurityMiddleware
import ipaddress
class DNSSecurityMiddleware(SecurityMiddleware):
def __init__(self, get_response):
super().__init__(get_response)
# Define trusted internal networks
self.trusted_networks = [
ipaddress.IPv4Network('10.0.0.0/8'),
ipaddress.IPv4Network('172.16.0.0/12'),
ipaddress.IPv4Network('192.168.0.0/16'),
ipaddress.IPv4Network('127.0.0.0/8'),
]
def __call__(self, request):
# Check X-Forwarded-For header for internal IPs
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', '')
if x_forwarded_for:
for ip_str in x_forwarded_for.split(','):
ip = ipaddress.ip_address(ip_str.strip())
if any(ip in network for network in self.trusted_networks):
# Reject requests appearing to come from internal networks
return JsonResponse({'error': 'Internal network access denied'}, status=403)
return self.get_response(request)
# Add to settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'yourproject.middleware.DNSSecurityMiddleware',
# ... other middleware
]For database connections, use Django's connection validation and avoid dynamic host construction:
# utils.py - Safe database connection
from django.db import connections
from django.db.utils import OperationalError
def safe_db_query(query, db_alias='default'):
"""Execute query with DNS rebinding protection"""
try:
# Verify connection is to allowed host
conn = connections[db_alias]
conn.ensure_connection()
cursor = conn.cursor()
cursor.execute(query)
return cursor.fetchall()
except OperationalError as e:
# Log and handle connection errors
logger.error(f'Database connection failed: {e}')
raise
except Exception as e:
logger.error(f'Database query failed: {e}')
raiseImplement Django's built-in security features:
# settings.py - Security configurations
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
# Disable internal service access through Django
INTERNAL_IPS = [] # Never allow internal IPs
ALLOWED_HOSTS = ['yourdomain.com'] # Restrict to specific domainsFor production deployments, use a Web Application Firewall (WAF) in front of Django to block DNS rebinding attempts at the network level. Combine this with regular middleBrick scans to ensure new vulnerabilities aren't introduced during development.
Frequently Asked Questions
How does DNS rebinding differ from SSRF in Django applications?
Can Django's built-in security features prevent DNS rebinding?
SECURE_SSL_REDIRECT and SECURE_BROWSER_XSS_FILTER help with general security but don't address DNS manipulation. You need additional middleware like the DNSSecurityMiddleware shown above, combined with proper network configuration and middleBrick scanning. The key is validating all external inputs and restricting internal service access through Django's configuration.