Documentation

Clickjacking (UI Redress Attack)

MEDIUM-HIGH SEVERITY UI REDRESS ATTACK OWASP TOP 10 RELATED

Overview

Clickjacking (also known as UI Redress Attack) is a web security vulnerability where an attacker tricks a user into clicking on something different from what they perceive, potentially causing the user to perform unintended actions.

Clickjacking Illustration

In One Sentence

Making you think you're clicking "Watch Cute Cats" when you're actually clicking "Transfer $10,000" on a hidden bank website underneath.

How It Works

The attacker creates a webpage with invisible or disguised elements

Places the legitimate target website in a transparent iframe over the attacker's page

When users think they're clicking on the attacker's page, they're actually clicking on the hidden iframe

The click registers on the legitimate site, performing actions without the user's knowledge

Important Context

The attack works because the legitimate site is actually loaded in the user's browser with their authenticated session, so actions performed are legitimate from the server's perspective.


Simple Explanation

Real-World Analogy

Imagine you're looking at a poster advertising a free concert. You reach out to click the "Get Free Tickets" button. But what you don't know is there's an invisible glass sheet in front of the poster with a different button that says "Donate $1,000" positioned exactly where you're about to click.

You think you're getting free concert tickets, but you're actually donating $1,000!

Visual Representation

What You See
┌─────────────────────────┐
│  WIN FREE iPHONE!       │
│                         │
│  [Click Here to Win]    │  ← You see this
│                         │
└─────────────────────────┘
What's Actually There
┌─────────────────────────┐
│  WIN FREE iPHONE!       │ ← Fake/Visible layer
│                         │
│  [Click Here to Win]    │
│                         │
└─────────────────────────┘
        ↓ Hidden underneath (invisible iframe)
┌─────────────────────────┐
│  Your Bank              │ ← Real/Invisible layer
│                         │
│  [Transfer $5,000]      │ ← What you actually click!
│                         │
└─────────────────────────┘

The Vulnerability

Basic Attack Example

html<!DOCTYPE html>
<html>
<head>
    <title>Win a Free iPhone!</title>
    <style>
        #target_website {
            position: absolute;
            top: -300px;
            left: -300px;
            opacity: 0.0;
            z-index: 2;
        }

        #decoy_button {
            position: absolute;
            top: 300px;
            left: 300px;
            z-index: 1;
        }
    </style>
</head>
<body>
    <h1>Click Below to Win a Free iPhone!</h1>

    <!-- Invisible iframe containing legitimate site -->
    <iframe id="target_website"
            src="https://vulnerable-bank.com/transfer"
            width="500" height="500">
    </iframe>

    <!-- Visible decoy button -->
    <button id="decoy_button">
        Click Here to Claim Your Prize!
    </button>
</body>
</html>

Critical Risk

When the user clicks the visible "prize" button, they're actually clicking the "Confirm Transfer" button on the hidden banking site, transferring money to the attacker.

Attack Flow

User clicks on malicious link: evil.com/free-prize

Browser renders:
  Layer 1 (z-index: 1): Decoy content (visible)
  Layer 2 (z-index: 2): Victim iframe (opacity: 0, invisible)

Your click at coordinates (250, 220): ↓ Hits Layer 2 (iframe) first (higher z-index)

Click registered on legitimate site's "Confirm" button inside iframe

Transaction/action executes without user's knowledge


Attack Scenarios

Scenario 1: Banking Fraud

html<style>
    #bank-iframe {
        position: absolute;
        opacity: 0;
        width: 1000px;
        height: 800px;
        top: -400px;
        left: -200px;
    }
</style>

<!-- Decoy content -->
<h1>WIN $1,000!</h1>
<button style="position: absolute; top: 450px; left: 250px;">
    CLICK TO CLAIM
</button>

<!-- Invisible bank iframe (if user already logged in) -->
<iframe id="bank-iframe" src="https://bank.com/transfer">
    <!-- Hidden form with pre-filled data:
         To: Attacker's account
         Amount: $5,000
         [Confirm Transfer] button positioned at (450, 250) -->
</iframe>

Protection Note

Most modern banks now implement X-Frame-Options to prevent this.

Scenario 2: Social Media Likejacking

Make victim like/follow attacker's page without consent

html<iframe src="https://social-network.com/pages/attacker-page"
        style="opacity:0.001; position:absolute; z-index:10;">
</iframe>

<div style="position:relative; z-index:1;">
    <h1>Take Our Quiz!</h1>
    <button>Start Quiz</button>  <!-- Actually clicking "Like" -->
</div>

Victim unknowingly likes attacker's page, giving attacker legitimacy and reach

Scenario 3: Permission Hijacking

Attacker's Goal

Trick user into granting webcam/microphone permissions

Attack Setup
html<iframe src="https://legitimate-site.com/enable-features"
        style="opacity:0; position:fixed; top:0; left:0; width:100%; height:100%;">
</iframe>

<div style="position:relative;">
    <h1>Download Free Game</h1>
    <button style="padding:20px;">Download Now</button>
</div>
Result

User clicks "Download Now" but actually clicks "Allow" on browser permission prompt in hidden iframe


Advanced Techniques

1. Drag-and-Drop Clickjacking

Tricks users into dragging content from a visible element into an invisible iframe

html<div draggable="true" ondragstart="drag(event)">
    Drag me to the target!
</div>

<iframe id="target"
        src="https://email-service.com/compose"
        style="opacity:0; position:absolute;">
</iframe>

User drags visible content but actually drops sensitive data into attacker-controlled form

2. Double Clickjacking

Requires two clicks to bypass confirmation dialogs

html<!-- First click: Open dialog -->
<iframe src="https://site.com/delete-account" style="opacity:0;">
</iframe>

<!-- Second click: Confirm dialog -->
<iframe src="https://site.com/confirm-delete" style="opacity:0;">
</iframe>

<div>
    <button>Click</button>
    <button>Confirm</button>
</div>

Implement delays between confirmation steps or require additional verification

3. Partial Transparency Attack

Uses very low opacity instead of complete invisibility to evade some detection methods

html<iframe src="https://target.com"
        style="opacity:0.001; /* Nearly invisible but not zero */
               filter: alpha(opacity=0.1);"> /* IE fallback */
</iframe>

Detection Evasion

Setting opacity to 0.001 instead of 0 can bypass naive detection scripts that only check for opacity:0

4. Touch-Based Clickjacking (Mobile)

Mobile-specific attacks exploiting touch interfaces (Tap-jacking)

html<!-- Mobile website -->
<div style="position: relative;">
    <h1>CLAIM YOUR FREE GIFT!</h1>
    <button>TAP HERE</button>

    <!-- Invisible iframe with "Authorize App" button -->
    <iframe src="https://mobile.facebook.com/dialog/oauth..."
            style="position: absolute; opacity: 0;">
    </iframe>
</div>

User taps "CLAIM FREE GIFT" but actually taps "Authorize App", granting attacker access to their account


Real-World Examples

Case Study 1: Adobe Flash Settings (2008)

Tricking users into enabling webcam/microphone without consent

html<style>
    #flash-settings {
        position: absolute;
        opacity: 0.01;
    }
</style>

<h1>Watch This Amazing Video!</h1>
<button>Play Video</button>

<!-- Adobe Flash settings dialog (invisible) -->
<object id="flash-settings">
    <!-- Flash security settings with "Allow" button
         positioned exactly where "Play Video" appears -->
</object>

Adobe added clickjacking protections to Flash Player permission dialogs

Case Study 2: Twitter "Don't Click" Worm (2009)

Viral clickjacking spreading through Twitter

1. User sees tweet: "Don't Click: http://bit.ly/xxxxxx"
2. Curious user clicks link
3. Page shows: "Don't click this button!"
4. Invisible Twitter iframe overlay
5. User clicks "Don't Click" button
6. Actually clicks Twitter "Retweet" button (hidden)
7. Tweet spreads to user's followers
8. Spreads virally across Twitter

Hundreds of thousands of retweets before Twitter blocked it

Case Study 3: Facebook Likejacking (2010-2011)

Mass "Like" fraud for viral pages

html<!-- Viral page: "OMG! Checkout Who Died!" -->
<iframe src="https://facebook.com/plugins/like.php?href=scam-page"
        style="opacity: 0; position: absolute;">
</iframe>

<button>See Who Died</button>
  • User clicks "See Who Died" → Actually clicks invisible "Like" button
  • Page appears in their timeline
  • Friends see it and click
  • Spreads exponentially
  • Millions of fraudulent "Likes"
  • Scam pages gaining huge audiences
  • Used for phishing and malware distribution

Facebook implemented X-Frame-Options header


Prevention & Mitigation

1. X-Frame-Options Header (Primary Defense)

apache# Deny all framing
Header always set X-Frame-Options "DENY"

# Or allow only same origin
Header always set X-Frame-Options "SAMEORIGIN"

# Or allow specific domain (deprecated)
Header always set X-Frame-Options "ALLOW-FROM https://trusted-site.com"

Best Practice

X-Frame-Options: SAMEORIGIN is recommended for most applications that don't need to be framed by external sites.

  • DENY - Cannot be framed by any site (most secure)
  • SAMEORIGIN - Can only be framed by pages from same origin
  • ALLOW-FROM uri - (Deprecated) Allowed specific origin only

2. Content Security Policy (Modern Approach)

apache# Most restrictive - no framing allowed
Header always set Content-Security-Policy "frame-ancestors 'none'"

# Allow same origin only
Header always set Content-Security-Policy "frame-ancestors 'self'"

# Allow specific domains
Header always set Content-Security-Policy "frame-ancestors 'self' https://trusted-site.com"

Important Note

CSP frame-ancestors is more flexible and modern than X-Frame-Options. Use both for maximum compatibility.

  • More flexible (supports multiple domains)
  • Standardized and future-proof
  • Better browser support for complex scenarios
  • Granular control over framing contexts
python@app.after_request
def set_frame_protection(response):
    # Legacy browsers
    response.headers['X-Frame-Options'] = 'DENY'

    # Modern browsers
    response.headers['Content-Security-Policy'] = "frame-ancestors 'none'"

    return response

3. Client-Side Frame Busting (Backup Only)

Critical Warning

Client-side frame busting alone is NOT sufficient as it can be bypassed. Always use server-side headers as primary defense.

javascript// Detect if page is in iframe
if (window.top !== window.self) {
    // Break out of iframe
    window.top.location = window.self.location;
}

Attackers can bypass frame-busting:

html<!-- Attacker disables JavaScript in iframe -->
<iframe src="https://victim.com" sandbox="allow-forms">
</iframe>

<!-- Victim's frame-busting JavaScript won't run! -->
javascriptapp.use(session({
    secret: 'your-secret',
    cookie: {
        sameSite: 'strict',  // Prevents cookie from being sent in cross-site requests
        secure: true,         // HTTPS only
        httpOnly: true        // Prevents JavaScript access
    }
}));
  • Strict - Cookie never sent in cross-site requests (including iframes)
  • Lax - Cookie sent in top-level navigation, not in iframes
  • None - Cookie sent everywhere (requires Secure flag)
html<!-- Attacker's clickjacking page -->
<iframe src="https://bank.com/transfer"></iframe>

<!-- With SameSite=Strict:
     Bank's session cookie NOT sent in iframe
     User appears logged out in iframe
     Transfer button doesn't work
     Attack fails! ✓ -->

5. User Interaction Confirmation

python@app.route('/transfer', methods=['POST'])
def transfer_money():
    # Check if request is from iframe
    if request.headers.get('Sec-Fetch-Dest') == 'iframe':
        return 'Transfers cannot be performed in frames', 403

    # For sensitive actions, require re-authentication
    if is_sensitive_action():
        if not recently_authenticated(session):
            return redirect('/reauth?next=/transfer')

    # Require CAPTCHA or additional confirmation
    if not verify_captcha(request.form['captcha']):
        return 'CAPTCHA required', 400

    # Proceed with transfer
    perform_transfer()
    return 'Transfer successful'

Testing for Clickjacking

Manual Testing

html<!DOCTYPE html>
<html>
<head>
    <title>Clickjacking Test</title>
</head>
<body>
    <h1>Clickjacking Vulnerability Test</h1>
    <p>Testing: <span id="target-url"></span></p>

    <iframe id="test-frame"
            src="https://target-site.com"
            width="800"
            height="600"
            style="border: 2px solid red;">
    </iframe>

    <script>
        document.getElementById('target-url').textContent =
            document.getElementById('test-frame').src;
    </script>
</body>
</html>
  • Vulnerable: Page loads inside iframe ✗
  • Protected: Browser blocks iframe or shows blank frame ✓

Testing with cURL

bashcurl -I https://target-site.com | grep -i "x-frame-options"

# Expected for protected site:
# X-Frame-Options: DENY
# or
# X-Frame-Options: SAMEORIGIN

Browser Developer Tools

Press F12 or right-click → Inspect

Click on the Network tab in developer tools

Navigate to the target website

Click on the main document request and look for:

  • X-Frame-Options
  • Content-Security-Policy
javascriptif (window.top === window.self) {
    console.log("Not framed - test with iframe");
} else {
    console.log("Currently in frame");
}

Automated Testing Tools

OWASP ZAP
bash# Install OWASP ZAP
# Run passive scan
zap-cli quick-scan https://target-site.com

# Check for clickjacking vulnerabilities in report
Burp Suite
  1. Configure browser proxy
  2. Browse target site
  3. Check Proxy → HTTP History
  4. Look for missing X-Frame-Options headers
Nikto
bashnikto -h https://target-site.com -Tuning 7

# Output will show if X-Frame-Options is missing
Online Tools
  • securityheaders.com - Checks for clickjacking protection headers
  • clickjacker.io - Automated clickjacking test

Common Mistakes

1. Only Using Client-Side Protection

Wrong Approach

javascript// This alone is NOT sufficient
if (window.top !== window.self) {
    window.top.location = window.self.location;
}

Correct Approach

apache# Always use server-side headers first
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"

Then add client-side as additional layer.

2. Inconsistent Header Deployment

Wrong
apache# Only set on homepage
<Location "/">
    Header set X-Frame-Options "DENY"
</Location>
Correct
apache# Set on ALL responses (note: "always")
Header always set X-Frame-Options "DENY"

3. Using ALLOW-FROM with Multiple Domains

Wrong

apache# ALLOW-FROM only supports ONE domain
Header set X-Frame-Options "ALLOW-FROM https://site1.com https://site2.com"

Correct

apache# Use CSP for multiple domains
Header set Content-Security-Policy "frame-ancestors https://site1.com https://site2.com"

4. Forgetting Error Pages

Common Issue

Security headers missing on 404, 500, and other error pages

Solution

apache# Use "always" to include error responses
Header always set X-Frame-Options "SAMEORIGIN"

Advanced Defense Strategies

1. UI Confirmation Patterns

javascript// Show clear visual feedback for sensitive actions
function showSecurityConfirmation(action) {
    // Dim background
    document.body.classList.add('security-modal-active');

    // Show prominent, unobscurable confirmation
    const modal = createModal({
        title: `Confirm ${action}`,
        style: 'security-critical',
        position: 'center-fixed',
        zIndex: 999999  // High z-index
    });

    return modal.getResponse();
}

2. Mouse Movement Analysis

javascript// Detect suspicious click patterns that might indicate clickjacking
let clickTracker = {
    lastMouseMove: null,
    clickHistory: []
};

document.addEventListener('mousemove', (e) => {
    clickTracker.lastMouseMove = Date.now();
});

document.addEventListener('click', (e) => {
    const timeSinceMove = Date.now() - clickTracker.lastMouseMove;

    // Suspicious: Click without recent mouse movement
    if (timeSinceMove > 100) {
        console.warn('Suspicious click pattern detected');
        // Require additional verification
        e.preventDefault();
        showSecurityChallenge();
    }

    clickTracker.clickHistory.push({
        time: Date.now(),
        x: e.clientX,
        y: e.clientY
    });
});

Complete Security Checklist

Server Configuration

  • [ ] Set X-Frame-Options header to DENY or SAMEORIGIN
  • [ ] Set Content-Security-Policy: frame-ancestors directive
  • [ ] Configure both headers for maximum browser compatibility
  • [ ] Test headers are present in all responses (including error pages)
  • [ ] Set SameSite=Strict or SameSite=Lax on session cookies
  • [ ] Enable HTTPS for all pages (Secure cookie flag)

Application Code

  • [ ] Implement frame-busting JavaScript as secondary defense
  • [ ] Require password re-authentication for sensitive operations
  • [ ] Use CSRF tokens on all state-changing requests
  • [ ] Validate Origin and Referer headers server-side
  • [ ] Implement rate limiting on sensitive endpoints
  • [ ] Log and monitor unusual framing attempts

Testing & Validation

  • [ ] Test with manual HTML iframe embedding
  • [ ] Verify headers with cURL/browser tools
  • [ ] Test in multiple browsers (Chrome, Firefox, Safari, Edge)
  • [ ] Automated scanning with OWASP ZAP or Burp Suite
  • [ ] Penetration testing for clickjacking vulnerabilities
  • [ ] Regression testing after code deployments

User Protection

  • [ ] Educate users about suspicious links/pages
  • [ ] Implement visual security indicators
  • [ ] Use multi-factor authentication for sensitive operations
  • [ ] Display action confirmations clearly
  • [ ] Show transaction summaries before final confirmation

Key Takeaways

Primary Defenses

  1. Always use X-Frame-Options: SAMEORIGIN or DENY - Prevents page from being framed
  2. Implement Content-Security-Policy: frame-ancestors - Modern, flexible protection
  3. Use both headers - Maximum browser compatibility
  4. Set SameSite cookie attribute - Prevents cookies in cross-site contexts

Critical Points

  • Client-side frame-busting alone is NOT sufficient (can be bypassed)
  • Server-side headers are the primary defense
  • Test on ALL pages including error pages
  • Require re-authentication for sensitive operations

Additional Layers

  • User education about phishing tactics
  • Visual security indicators for sensitive actions
  • Multi-factor authentication
  • Rate limiting on critical endpoints
  • Monitoring and alerting for suspicious activity

References & Resources

Official Documentation

Testing Tools

Learning Resources


Layerd AI Protection

Layerd AI Cloud WAF prevents clickjacking:

  • Automatic header injection - Adds X-Frame-Options and CSP
  • Frame detection - Identifies suspicious iframe patterns
  • Real-time protection - Blocks malicious framing attempts
  • Zero configuration - Works out of the box

Learn more about Layerd AI Protection →


Remember: Clickjacking is a UI redress attack that tricks users into performing unintended actions. Always implement server-side header protection (X-Frame-Options and CSP frame-ancestors) as your primary defense, supplemented with client-side protections and user education.

Stay protected by implementing multiple layers of defense!


Last updated: November 2025