Clickjacking Vulnerability Due to Missing Frame Protection
What Is This Vulnerability?
Clickjacking is an attack where a malicious page loads your application inside a transparent or disguised iframe and tricks users into clicking buttons they cannot see. Without frame protection headers, an attacker can overlay your authenticated pages and capture clicks intended for invisible UI elements like delete buttons, payment confirmations, or permission grants.
Why It Happens
Browsers allow any page to embed another page in an iframe by default. Unless the embedded page explicitly sends X-Frame-Options or a CSP frame-ancestors directive, the browser has no way to know the embedding is unauthorized. Many applications omit these headers because they are not required for basic functionality.
Example Code
<!-- Attacker's page: the victim's app loads in a transparent iframe -->
<html>
<body>
<h1>Click here to win a prize!</h1>
<iframe
src="https://victim-app.com/settings/delete-account"
style="opacity: 0; position: absolute; top: 0; left: 0;
width: 100%; height: 100%; z-index: 10;"
></iframe>
</body>
</html>import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
const response = NextResponse.next();
response.headers.set("X-Frame-Options", "DENY");
response.headers.set(
"Content-Security-Policy",
"frame-ancestors 'none'",
);
return response;
}How Hackers Exploit It
An attacker creates an enticing page (fake contest, video player, login form) and loads your app in a hidden iframe positioned over clickable elements. When the victim clicks what appears to be a harmless button, they are actually clicking a button in your app. This can trigger account deletion, money transfers, or permission changes without the victim's knowledge.
How to Fix It
Set the X-Frame-Options header to DENY on all responses. For more granular control, use the CSP frame-ancestors directive (e.g., frame-ancestors 'self') to specify which domains may embed your site. Both headers should be set for backward compatibility with older browsers.