Open Redirect in Django with Mongodb
Open Redirect in Django with Mongodb — how this specific combination creates or exposes the vulnerability
An open redirect occurs when an application accepts a user-supplied URL or path and redirects the browser to that location without adequate validation. In Django, this commonly arises when a view reads a next or redirect_to query parameter and uses HttpResponseRedirect or redirect without verifying the target. When MongoDB is used as the primary data store, the risk pattern is similar, but MongoDB-specific data flows can inadvertently surface redirect targets or influence routing decisions.
Consider a Django app that stores public-facing pages or redirect rules in a MongoDB collection. A view might fetch a document by slug and redirect to a URL stored in a field such as external_url. If that field is user-controllable or insufficiently validated, an attacker can craft a link like /go?slug=login&next=https://evil.com, and the view may redirect the victim to the malicious site. Even when using MongoDB only for session or user preferences, if the application dynamically builds redirect locations using data retrieved from MongoDB (e.g., tenant-specific landing pages), missing allow-list checks can enable open redirects.
Django’s own URL validation utilities, such as django.utils.http.is_safe_url (deprecated in newer versions) or url_has_allowed_host_and_scheme, should be applied to any destination derived from MongoDB documents. Failing to enforce a strict allow-list of trusted domains, or relying solely on format checks, means an attacker can supply a full external URL stored in MongoDB or injected via form/query parameters. The presence of MongoDB does not inherently create the flaw, but it can provide the vector for storing or selecting redirect targets that bypass developer assumptions.
In API-style views that return 302 responses based on MongoDB-stored configurations, the absence of runtime schema validation or strict referential integrity can allow an attacker to manipulate redirect logic through modified documents or unexpected query parameters. Because the scan checks include input validation and security misconfigurations, it will flag cases where redirect targets are derived from unchecked inputs or external data stores without host allow-listing.
Mongodb-Specific Remediation in Django — concrete code fixes
Remediation focuses on validating redirect targets against an allow-list of trusted hosts and avoiding direct use of user-supplied values or MongoDB-stored URLs without strict checks. Below are concrete patterns and working MongoDB-based examples for Django using pymongo.
1. Validate against an allow-list of hosts
Always resolve the final target through a controlled mapping rather than using raw values from MongoDB. For example, define a dictionary of approved slugs to safe URLs and look up the destination server-side before issuing a redirect.
import pymongo
from django.http import HttpResponseRedirect
from urllib.parse import urlparse
client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["myapp"]
redirect_map = db.get_collection("redirect_map") # stores allowed slug -> internal path mapping
def safe_redirect_view(request):
slug = request.GET.get("slug")
# Fetch only pre-approved redirect mappings from MongoDB
doc = redirect_map.find_one({"slug": slug})
if not doc:
return HttpResponseRedirect("/") # default safe location
# Ensure the stored target is internal or explicitly allowed
target = doc.get("path", "/")
# Further validate internal paths or use a hardcoded mapping
return HttpResponseRedirect(target)
2. Use Django’s host validation for external URLs
If MongoDB stores external URLs, validate the host against a strict allow-list before redirecting. Do not trust format checks alone.
from django.http import HttpResponseRedirect
from urllib.parse import urlparse
TRUSTED_HOSTS = {"app.example.com", "static.example.com", "cdn.example.com"}
def validate_safe_url(url):
parsed = urlparse(url)
return parsed.hostname in TRUSTED_HOSTS and parsed.scheme in {"http", "https"}
def redirect_with_external_url(request):
external_url = request.GET.get("url")
if external_url and validate_safe_url(external_url):
return HttpResponseRedirect(external_url)
return HttpResponseRedirect("/fallback")
3. Avoid storing redirect targets in user-modifiable documents
Design MongoDB documents so that redirect targets are derived from immutable, server-controlled mappings rather than user-editable fields. For tenant-specific flows, store tenant identifiers and resolve paths server-side.
def tenant_redirect_view(request, tenant_id):
tenants = db.get_collection("tenants")
tenant = tenants.find_one({"_id": tenant_id}, {"default_path": 1})
if not tenant:
return HttpResponseRedirect("/")
# Resolve to a known internal path; do not use tenant-supplied external URLs
return HttpResponseRedirect(tenant.get("default_path", "/"))
4. Use framework-level helpers with MongoDB-aware data
When using modern Django, prefer redirect with validated arguments and keep MongoDB lookups restricted to safe identifiers.
from django.shortcuts import redirect
def profile_redirect(request):
user_id = request.GET.get("user_id")
# Fetch user document from MongoDB to confirm existence, but do not use raw URL fields
users = db.get_collection("users")
user = users.find_one({"_id": user_id}, {"username": 1})
if user:
return redirect("profile_detail", username=user["username"])
return redirect("home")
5. Enforce same-origin or relative paths
For maximum safety, restrict redirects to relative paths or same-origin locations when MongoDB values are involved. This neutralizes open redirect risks even if a malicious document entry exists.
def relative_redirect_view(request):
# Compute a path internally; do not forward raw external URLs
section = request.GET.get("section", "home")
# Validate section against an allow-list stored or computed server-side
allowed_sections = {"dashboard", "settings", "help"}
if section in allowed_sections:
return redirect(f"/{section}")
return redirect("/")