Weakness reference
CWE-1021

Improper Restriction of Rendered UI Layers or Frames (Clickjacking)

Clickjacking is a deceptive technique where an attacker embeds your website in a hidden frame on a malicious page, then overlays fake buttons or content on…

01Summary

Clickjacking is a deceptive technique where an attacker embeds your website in a hidden frame on a malicious page, then overlays fake buttons or content on top. When users click what they think is a legitimate button, they're actually clicking something on your site — often performing sensitive actions like changing passwords, transferring funds, or granting permissions without realizing it. This weakness exists when a site doesn't tell browsers whether it's safe to be framed by other domains.

02How It Happens

A web application becomes vulnerable to clickjacking when it fails to set HTTP headers or frame-busting JavaScript that restrict how and where the page can be embedded. Browsers have no built-in protection against framing by default — it's the responsibility of each site to opt out. An attacker can then create a page that loads your site in an invisible <iframe>, position it precisely behind transparent overlays, and use CSS tricks to make their fake interface visible while your real interface remains hidden but clickable. The user's browser, following normal security rules, allows the click to reach your site because the user is technically interacting with content from your domain — they just can't see it.

03Real-World Impact

Clickjacking can lead to unauthorized account actions: a user might unknowingly grant browser permissions (camera, microphone, location), change account settings, approve financial transactions, or delete data. In high-stakes contexts like banking or email, a single click can compromise an entire account. The attack is particularly dangerous because it requires no malware, no phishing email, and no technical sophistication from the victim — just a visit to an attacker's page.

04Vulnerable & Fixed Patterns

Vulnerable pattern
from flask import Flask, render_template

app = Flask(__name__)

@app.route('/settings')
def settings():
    # No frame-restriction headers set
    return render_template('settings.html')

if __name__ == '__main__':
    app.run()

Why it's vulnerable:
The application does not set X-Frame-Options or Content-Security-Policy headers, so any external site can embed this page in an iframe and overlay deceptive content on top of it.

Fixed pattern
from flask import Flask, render_template

app = Flask(__name__)

@app.after_request
def set_security_headers(response):
    response.headers['X-Frame-Options'] = 'SAMEORIGIN'
    response.headers['Content-Security-Policy'] = "frame-ancestors 'self'"
    return response

@app.route('/settings')
def settings():
    return render_template('settings.html')

if __name__ == '__main__':
    app.run()
Vulnerable pattern
<?php
// No frame-restriction headers set
header('Content-Type: text/html; charset=utf-8');
?>
<html>
<head><title>Account Settings</title></head>
<body>
  <form method="POST" action="/update-settings">
    <input type="hidden" name="action" value="change_password">
    <button type="submit">Update Settings</button>
  </form>
</body>
</html>

Why it's vulnerable:
The page sends no X-Frame-Options or Content-Security-Policy header, allowing it to be embedded in an iframe on any external domain.

Fixed pattern
<?php
header('X-Frame-Options: SAMEORIGIN');
header("Content-Security-Policy: frame-ancestors 'self'");
header('Content-Type: text/html; charset=utf-8');
?>
<html>
<head><title>Account Settings</title></head>
<body>
  <form method="POST" action="/update-settings">
    <input type="hidden" name="action" value="change_password">
    <button type="submit">Update Settings</button>
  </form>
</body>
</html>

05Prevention Checklist

Set X-Frame-Options: SAMEORIGIN on all pages to allow framing only by your own domain, or DENY to block framing entirely.
Use Content-Security-Policy: frame-ancestors 'self' as a modern, more flexible alternative (or in addition to X-Frame-Options).
Apply these headers globally via your web server configuration (nginx, Apache) or application middleware, not page-by-page.
Test that your site cannot be embedded in an iframe on an external domain — use browser developer tools or a simple test page.
For sensitive actions (password changes, fund transfers, permission grants), consider additional protections like CSRF tokens and user confirmation dialogs.

06Signs You May Already Be Affected

Look for unexpected user reports of unintended account changes or permission grants they don't recall authorizing. Check your web server logs for requests to sensitive endpoints (password changes, settings updates) that lack a clear user-initiated referrer or come from unusual patterns. If you can embed your site in an iframe on a test page without errors, your frame-restriction headers are not set.

07Related Recent Vulnerabilities