Vulnerability
Django

Clickjacking in Django Applications

Django includes XFrameOptionsMiddleware and sets X_FRAME_OPTIONS to DENY by default since Django 3.0, providing good baseline clickjacking protection. However, vulnerabilities appear when developers add @xframe_options_exempt to views, remove XFrameOptionsMiddleware, or override the setting to SAMEORIGIN without understanding the implications.

Scan Your Django App

How Clickjacking Manifests in Django

While Django has strong defaults, clickjacking vulnerabilities appear in these scenarios: Developers who need to embed their app in an iframe (for widget features, preview panes, or third-party integrations) remove XFrameOptionsMiddleware from MIDDLEWARE entirely instead of exempting specific views. This removes protection from the entire application. The @xframe_options_exempt decorator is used too broadly. Developers apply it to entire views or view classes when only a specific endpoint needs to be framed, exposing state-changing pages to clickjacking. Django projects upgraded from pre-3.0 versions may have X_FRAME_OPTIONS = 'SAMEORIGIN' in settings.py from the old default. SAMEORIGIN allows the same domain to frame the page, which can be exploited if the attacker finds an XSS vulnerability on the same domain. Django REST Framework views return JSON by default, but the browsable API renderer serves HTML that can be clickjacked if XFrameOptionsMiddleware is not in the middleware stack.

Real-World Impact

A Django application removed XFrameOptionsMiddleware because a partner needed to embed a widget page. The entire application lost clickjacking protection. An attacker embedded the account settings page in an iframe on a phishing site, tricking users into clicking "Change Email" which changed their email to the attacker's, enabling an account takeover through password reset. A Django admin interface was deployed with X_FRAME_OPTIONS = 'SAMEORIGIN' (the pre-3.0 default). An XSS vulnerability on a public page of the same domain allowed the attacker to create an iframe to the admin panel, clickjacking admin users into performing destructive actions.

Step-by-Step Fix

1

Verify Django clickjacking defaults

Ensure XFrameOptionsMiddleware is in MIDDLEWARE and X_FRAME_OPTIONS is set to DENY.

# settings.py

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',  # Must be present
    # ... other middleware
]

# Django 3.0+ defaults to DENY, but verify it is not overridden
X_FRAME_OPTIONS = 'DENY'
2

Use per-view exemptions instead of removing middleware

When specific views must be embeddable, exempt only those views and add CSP frame-ancestors for allowlisting.

# views.py
from django.views.decorators.clickjacking import (
    xframe_options_exempt,
    xframe_options_deny,
)
from django.http import HttpResponse

# This view can be embedded (widget/embed only)
@xframe_options_exempt
def embeddable_widget(request):
    # Set CSP frame-ancestors for this specific view
    response = render(request, 'widget.html')
    response['Content-Security-Policy'] = (
        "frame-ancestors 'self' https://trusted-partner.com"
    )
    return response

# Explicitly mark critical views as non-frameable
@xframe_options_deny
def delete_account(request):
    # Extra protection for dangerous actions
    return render(request, 'delete_account.html')
3

Add CSP frame-ancestors with django-csp

Use django-csp for modern clickjacking protection that supports domain allowlists.

# pip install django-csp

# settings.py
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'csp.middleware.CSPMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # ... other middleware
]

# Block framing by default
CSP_FRAME_ANCESTORS = ("'none'",)

# Keep X_FRAME_OPTIONS as fallback for older browsers
X_FRAME_OPTIONS = 'DENY'

# For views that need specific frame-ancestors:
# from csp.decorators import csp_update
# @csp_update(FRAME_ANCESTORS=["'self'", "https://trusted.com"])
# def my_embeddable_view(request): ...

Prevention Best Practices

1. Keep XFrameOptionsMiddleware in MIDDLEWARE and X_FRAME_OPTIONS = 'DENY' in settings.py. 2. Use @xframe_options_exempt only on specific views that must be embeddable, never on state-changing views. 3. Add CSP frame-ancestors via django-csp for modern browser protection. 4. Use @xframe_options_deny explicitly on critical views as defense in depth. 5. Never remove XFrameOptionsMiddleware — use per-view decorators instead.

How to Test

1. Run python manage.py check --deploy and look for clickjacking warnings. 2. Check that X_FRAME_OPTIONS is set to DENY in settings.py (not SAMEORIGIN). 3. Search for @xframe_options_exempt in your codebase and verify each usage is justified. 4. Create an HTML file with <iframe src="https://your-django-app.com"></iframe> and verify it is blocked. 5. Use Vibe App Scanner to automatically detect clickjacking vulnerabilities in your Django application.

Frequently Asked Questions

Does Django prevent clickjacking by default?

Yes, since Django 3.0. XFrameOptionsMiddleware is included in the default MIDDLEWARE list for new projects, and X_FRAME_OPTIONS defaults to DENY. However, projects created before Django 3.0 may still have SAMEORIGIN, and any project can have the middleware removed or overridden.

What is the difference between DENY and SAMEORIGIN in Django?

DENY blocks the page from being framed by any site, including your own domain. SAMEORIGIN allows pages on the same domain to frame each other. Use DENY unless your application has a legitimate need to iframe its own pages (like a page builder or preview pane). SAMEORIGIN is weaker because an XSS vulnerability on any page of your domain could create a clickjacking frame.

Should I use XFrameOptionsMiddleware or CSP frame-ancestors?

Use both. XFrameOptionsMiddleware provides backward compatibility for older browsers that do not support CSP. CSP frame-ancestors is the modern standard and supports domain allowlists (which X-Frame-Options cannot do). When both are present, modern browsers use frame-ancestors and ignore X-Frame-Options.

Is Your Django App Vulnerable to Clickjacking?

VAS automatically scans for clickjacking vulnerabilities in Django applications and provides step-by-step remediation guidance with code examples.

Scans from $5, results in minutes. Get actionable fixes tailored to your Django stack.