X-Frame-Options: Your Shield Against Clickjacking Attacks
What is it?
X-Frame-Options is an HTTP response header that controls whether a web page can be embedded within an iframe, frame, or object element on another website. Introduced in 2008 by Microsoft for Internet Explorer, it has since become a standard security mechanism supported by all modern browsers.
The header accepts three directives:
DENY: The page cannot be embedded in any frame, regardless of the site attempting to do soSAMEORIGIN: The page can only be embedded in frames on the same origin (same domain, protocol, and port)ALLOW-FROM uri: (Deprecated) The page can only be embedded in frames on the specified origin
In practice, most security-conscious websites use either DENY or SAMEORIGIN depending on their legitimate framing needs.
Why does it matter?
Without X-Frame-Options protection, malicious actors can embed your website within invisible or deceptive frames on their own domains. This technique, known as clickjacking, tricks users into performing actions they never intended to execute.
Consider a banking website without frame protection. An attacker creates a malicious page that loads the bank's "Transfer Money" form in a transparent iframe positioned over fake content. The user thinks they're clicking a button to "See Cute Cat Photos," but they're actually clicking the hidden "Confirm Transfer" button underneath.
The absence of X-Frame-Options opens your users to:
- Unauthorized transactions and fund transfers
- Unwanted social media posts or profile changes
- Installation of malicious browser extensions
- Disclosure of sensitive information through form submissions
- Account settings modifications without user knowledge
Even worse, these attacks require no software vulnerability in your application—they exploit the browser's default behavior of allowing any page to be framed.
How attacks work
Clickjacking attacks rely on carefully constructed HTML that positions malicious content deceptively. Here's how a typical attack unfolds:
Basic Transparent Overlay Attack
<!DOCTYPE html>
<html>
<head>
<title>Win a Free iPhone!</title>
<style>
iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.0001; /* Nearly invisible */
z-index: 1000;
}
.fake-button {
position: absolute;
top: 200px;
left: 300px;
z-index: 1;
}
</style>
</head>
<body>
<h1>Congratulations! Click below to claim your prize!</h1>
<button class="fake-button">Click Here to Claim</button>
<!-- Victim site loaded invisibly, with real "Delete Account" button
positioned exactly over the fake button -->
<iframe src="https://victim-site.com/account/delete"></iframe>
</body>
</html>
The attacker carefully positions the iframe so that the victim site's sensitive button aligns perfectly with the enticing fake button visible to the user.
Advanced Multi-Step Attacks
More sophisticated attacks use multiple overlays to guide users through multi-step processes:
- First click: Authenticate to a service the user is already logged into
- Second click: Navigate to a sensitive page
- Third click: Confirm a dangerous action
These attacks can be enhanced with:
- Cursor manipulation: Changing the cursor appearance to hide the true click target
- Drag-and-drop hijacking: Making users drag sensitive data into attacker-controlled fields
- Double-clickjacking: Requiring two clicks makes detection harder for automated tools
- Mobile exploitation: Smaller screens make overlay detection more difficult
Real-world incidents
Clickjacking has been exploited against major platforms:
Twitter Follow Hijacking (2009)
Security researcher Robert Hansen demonstrated a clickjacking attack that forced Twitter users to unknowingly follow an attacker's account. The attack embedded Twitter's "Follow" button in an invisible iframe over a fake video player. When users clicked "Play," they were actually clicking Twitter's hidden follow button.
This attack was particularly effective because:
- Users remained logged into Twitter in the background
- The action required only a single click
- No JavaScript execution was needed—pure HTML/CSS
- It spread virally as compromised accounts promoted the malicious page
Twitter responded by implementing X-Frame-Options: DENY, preventing their pages from being embedded anywhere.
Facebook Likejacking Campaign (2010)
Attackers used clickjacking to artificially inflate Facebook page likes and spread malicious links. Victims encountered enticing headlines like "OMG! You Won't Believe What Happened Next!" with a visible button saying "See More."
Underneath the transparent iframe was Facebook's "Like" button. Each click simultaneously:
- Liked the attacker's Facebook page
- Shared the malicious link to the victim's timeline
- Exposed the victim's friends to the same attack
The campaign spread exponentially before Facebook implemented frame-busting defenses.
Adobe Flash Settings Manipulation (2008)
Before Flash's decline, attackers exploited Adobe's Flash Player Settings Manager page, which lacked frame protection. Attackers tricked users into enabling camera and microphone access for malicious Flash applications.
The attack overlay made it appear users were clicking to:
- Start a video
- Skip an advertisement
- Close a popup window
But they were actually clicking through to grant dangerous permissions in the hidden Flash settings panel.
What Nyambush detects
Nyambush's security header scanner checks for X-Frame-Options in your site's HTTP responses. Our detection engine:
- Sends HTTP requests to your domain and subdomains
- Analyzes response headers for the presence and configuration of X-Frame-Options
- Evaluates the directive (DENY, SAMEORIGIN, or missing)
- Checks for conflicts with Content-Security-Policy frame-ancestors directive
- Assigns a security score based on your configuration
Our scan identifies:
- Missing X-Frame-Options: Your site is fully vulnerable to clickjacking
- Misconfigured directives: Using deprecated ALLOW-FROM without CSP fallback
- Inconsistent protection: Some pages protected, others exposed
- Header conflicts: X-Frame-Options contradicting CSP frame-ancestors
For authenticated areas of your site (login pages, dashboards, admin panels), Nyambush flags missing X-Frame-Options as a critical vulnerability. For public content pages that legitimately need embedding, we provide guidance on safe configuration.
How to fix it
Implementing X-Frame-Options requires adding a single HTTP header to your responses. The configuration method depends on your web server and application framework.
Apache (.htaccess or httpd.conf)
# Prevent all framing
Header always set X-Frame-Options "DENY"
# Or allow same-origin framing only
Header always set X-Frame-Options "SAMEORIGIN"
Add this to your .htaccess file in the web root, or to your Apache configuration file. The always directive ensures the header is sent even for error responses.
Nginx
# In your server block or location block
server {
listen 80;
server_name example.com;
# Prevent all framing
add_header X-Frame-Options "DENY" always;
# Or allow same-origin framing only
# add_header X-Frame-Options "SAMEORIGIN" always;
}
The always parameter ensures the header is included in all responses, including redirects and errors.
Node.js / Express
const express = require('express');
const helmet = require('helmet');
const app = express();
// Using helmet middleware (recommended)
app.use(helmet.frameguard({ action: 'deny' }));
// Or manually
app.use((req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY');
next();
});
Next.js
// next.config.js
module.exports = {
async headers() {
return [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
],
},
];
},
};
PHP
<?php
// Add at the top of your entry file (e.g., index.php)
header('X-Frame-Options: DENY');
?>
Modern Approach: CSP frame-ancestors
While X-Frame-Options remains widely supported, the modern Content-Security-Policy header provides more granular control through the frame-ancestors directive:
Content-Security-Policy: frame-ancestors 'none';
This is equivalent to X-Frame-Options: DENY. The advantage of CSP is that you can specify multiple allowed origins:
Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com;
This allows embedding from your own origin and a specific trusted domain—something X-Frame-Options' deprecated ALLOW-FROM couldn't do reliably.
Best practice: Implement both headers for maximum compatibility:
X-Frame-Options: DENY
Content-Security-Policy: frame-ancestors 'none';
If both are present and conflict, browsers typically prioritize CSP's frame-ancestors directive.
Choosing DENY vs SAMEORIGIN
Use DENY when:
- Your site should never be embedded anywhere
- You have authentication pages, admin panels, or payment forms
- Security is the absolute priority
Use SAMEORIGIN when:
- You need to embed your own pages in frames (e.g., dashboard widgets)
- Your application uses legitimate iframes internally
- You control all the pages that might need framing
Testing Your Configuration
After implementing X-Frame-Options, verify it's working:
-
Check headers with curl:
curl -I https://your-site.com | grep -i x-frame-options -
Test in browser DevTools: Open DevTools (F12), navigate to your site, check the Network tab, and inspect response headers.
-
Attempt to frame your site: Create a test HTML file:
<iframe src="https://your-site.com"></iframe>Open it in a browser. If configured correctly, you should see an error in the console:
Refused to display 'https://your-site.com/' in a frame because it set 'X-Frame-Options' to 'deny'.
Summary
X-Frame-Options is a simple yet powerful defense against clickjacking attacks that can lead to unauthorized actions, data theft, and user deception. By adding a single HTTP header, you prevent malicious sites from embedding your pages in invisible or deceptive frames.
Implement X-Frame-Options: DENY for maximum protection on pages that should never be framed, or use SAMEORIGIN when your application requires legitimate same-origin framing. For modern browsers, complement X-Frame-Options with Content-Security-Policy's frame-ancestors directive for more granular control and better compatibility.
Nyambush automatically detects missing or misconfigured frame protection across your domains and subdomains, providing actionable recommendations to secure your users. With clickjacking prevention in place, you eliminate an entire class of attacks that require no vulnerability in your code—just missing security headers.