Clickjacking: When That 'Free iPad' Button Deletes Your Account ๐ฏ๐ชค
Clickjacking: When That 'Free iPad' Button Deletes Your Account ๐ฏ๐ชค
So there I was, reviewing a client's web app in my early security research days, when I found something wild: their entire admin panel could be overlaid on a fake "Win a Free iPhone!" page. One click from an admin = instant account takeover. And they had NO idea it was even possible.
Let me blow your mind with one of the sneakiest attacks in web security! ๐ญ
What Even IS Clickjacking? ๐ค
Imagine this: You see a button that says "Click for Free Stuff!" You click it. But underneath that button, invisible to you, is your bank's "Transfer All Money" button.
Congratulations - you just got clickjacked! ๐ธ
The technical name: UI Redress Attack (because "clickjacking" sounded too cool for academics)
The street name: The invisible button trap
What actually happens:
- Attacker puts YOUR website in an invisible iframe
- Overlays it with something enticing on THEIR website
- You click the fake button
- You actually clicked YOUR site's real button underneath
- Chaos ensues ๐ฅ
The Attack in Action ๐ฌ
Here's how easy it is (this is the BAD way - for educational purposes only!):
<!-- Evil attacker's website -->
<style>
iframe {
position: absolute;
top: 0;
left: 0;
opacity: 0.0001; /* Nearly invisible! */
width: 100%;
height: 100%;
z-index: 2; /* Sits on top */
}
.fake-button {
position: absolute;
top: 200px;
left: 300px;
z-index: 1; /* Sits below the iframe */
}
</style>
<!-- Your actual website, loaded invisibly -->
<iframe src="https://yourbank.com/transfer-money"></iframe>
<!-- Fake button they see -->
<button class="fake-button">
๐ CLAIM YOUR FREE iPAD! ๐
</button>
What the user sees: A shiny button promising free stuff
What the user clicks: Your actual "Transfer $1000" button hidden in the iframe
Result: Money goes bye-bye ๐๐ฐ
Real-World Disasters I've Seen ๐ฑ
Story #1: The Social Media Disaster
In security communities, we often discuss how one client had their entire "Delete Account" flow clickjackable. Someone made a prank page: "Click to see cute puppies!"
Hundreds of users accidentally deleted their accounts. They thought they were clicking puppy pictures. ๐๐
Story #2: The OAuth Nightmare
Another classic: Attackers overlaid a site's OAuth approval page. Users thought they were playing a game. They actually authorized a malicious app to access their data.
The attacker's button said: "Press SPACE to jump!"
What they actually pressed: "Grant All Permissions" on an invisible OAuth dialog
Big yikes. ๐ฌ
Story #3: The Webcam Permission Trick
This one still haunts me. Attacker overlaid browser's "Allow Webcam Access" permission. Made a fake "you're the 1,000,000th visitor!" page.
People clicking the celebration confetti button were actually enabling webcam access for the attacker's site.
In my experience building production systems, this is why we take UI security seriously!
How to STOP Clickjacking ๐ก๏ธ
The fix is actually super simple. Like, embarrassingly simple.
Fix #1: X-Frame-Options Header (The Old Reliable)
// In your Laravel middleware or headers
header('X-Frame-Options: DENY');
What it does: Tells browsers "DO NOT put my site in an iframe. EVER."
Options:
DENY- No iframes, periodSAMEORIGIN- Only allow iframes from your own domainALLOW-FROM https://trusted-site.com- Allow specific domains (deprecated, don't use)
Fix #2: Content-Security-Policy (The Modern Way)
// The fancy new approach
header("Content-Security-Policy: frame-ancestors 'none'");
CSP frame-ancestors options:
'none'- No iframes allowed (like X-Frame-Options: DENY)'self'- Only your own site (like SAMEORIGIN)https://trusted.com- Whitelist specific domains
Pro tip: Use BOTH headers for maximum compatibility!
// Belt AND suspenders approach
header('X-Frame-Options: DENY');
header("Content-Security-Policy: frame-ancestors 'none'");
Fix #3: Laravel Middleware (The Easy Way)
As someone passionate about security, I love that Laravel makes this dead simple:
// In your middleware
public function handle($request, Closure $next)
{
$response = $next($request);
$response->headers->set('X-Frame-Options', 'DENY');
$response->headers->set(
'Content-Security-Policy',
"frame-ancestors 'none'"
);
return $response;
}
Or even simpler - add to your global middleware:
// In App\Http\Kernel.php
protected $middleware = [
\Illuminate\Http\Middleware\FrameGuard::class, // Built-in!
];
Laravel's FrameGuard middleware sets X-Frame-Options: SAMEORIGIN by default. Nice! ๐
Fix #4: When You NEED Iframes (The Careful Way)
Sometimes you actually want your site embedded (embeddable widgets, etc.). Then do this:
// Only allow specific trusted domains
header("Content-Security-Policy: frame-ancestors 'self' https://trusted-partner.com");
But add this too:
// Frame-busting JavaScript (backup defense)
if (top !== self) {
top.location = self.location;
}
What this does: If your site detects it's in an iframe, it breaks out and loads in the top window. Ninja move! ๐ฅท
Advanced Defenses ๐ฅ
Defense #1: SameSite Cookies
// Make cookies resistant to clickjacking
setcookie('session', $value, [
'samesite' => 'Strict', // Only send on same-site requests
'secure' => true, // HTTPS only
'httponly' => true, // No JavaScript access
]);
Why this helps: Even if clickjacked, the malicious site can't ride your cookies!
Defense #2: User Interaction Verification
// For sensitive actions, require additional confirmation
public function deleteAccount(Request $request)
{
if (!$request->session()->get('confirmed_deletion')) {
return redirect()->route('confirm.delete');
}
// Multi-step process makes clickjacking harder
User::find($request->user()->id)->delete();
}
The idea: Make sensitive actions require multiple clicks/pages. Harder to clickjack!
Defense #3: CAPTCHA for Sensitive Actions
<!-- Before account deletion, money transfer, etc. -->
<form method="POST">
@csrf
{!! NoCaptcha::display() !!}
<button type="submit">Confirm Deletion</button>
</form>
Why: CAPTCHA can't be clickjacked because it requires user interaction with the visible page!
Testing Your Defense ๐งช
Quick test: Try embedding your site in an iframe:
<!-- test.html -->
<!DOCTYPE html>
<html>
<body>
<h1>Can this be embedded?</h1>
<iframe src="https://your-site.com" width="800" height="600"></iframe>
</body>
</html>
Expected result: Your site should NOT load in the iframe. You'll see a blank frame or error.
If your site DOES load: You're vulnerable! Go add those headers NOW! ๐จ
The Checklist โ
Protect your site from clickjacking:
- Set
X-Frame-Options: DENYheader (orSAMEORIGINif you need same-domain iframes) - Set
Content-Security-Policy: frame-ancestors 'none'header - Enable Laravel's FrameGuard middleware
- Use SameSite cookies
- Add multi-step confirmation for sensitive actions
- Test with the iframe test above
- Consider frame-busting JavaScript as backup
Common Mistakes I See ๐คฆโโ๏ธ
Mistake #1: "I'll just check the Referer header!"
Why it fails: Referer can be spoofed or missing. Not reliable!
Mistake #2: "I'll use JavaScript frame-busting only!"
Why it fails: JavaScript can be disabled. Always use HTTP headers as primary defense!
Mistake #3: "My site isn't important enough to attack!"
Why it fails: Attackers use BOTS. They scan EVERYTHING. Size doesn't matter!
Mistake #4: Setting headers on SOME pages but not all
Why it fails: Attackers will find the unprotected page. Be consistent!
Real Talk: When Iframes Are OK ๐ฌ
Q: "I need to embed YouTube videos. Is that clickjacking?"
A: No! That's YOU embedding THEIR site. Clickjacking is when someone embeds YOUR site on their page.
Q: "What if I WANT my content embeddable (like widgets)?"
A: Use frame-ancestors 'self' https://trusted-domain.com' to whitelist specific partners. Don't use DENY.
Q: "Can clickjacking steal passwords?"
A: Not directly - but it can trick users into clicking "Change Password" buttons, OAuth approvals, payment confirmations, etc.
Q: "Is this still relevant in 2026?"
A: YES! In security communities, we still see clickjacking vulnerabilities in bug bounty programs regularly. It's an easy-to-miss vulnerability.
The Bottom Line ๐ฏ
Clickjacking is like a magic trick - it uses misdirection to make you do something you didn't intend.
The good news: It's one of the EASIEST vulnerabilities to fix. Two HTTP headers and you're protected!
The bad news: SO many sites still don't implement it.
Your action items:
- Add those headers (5 minutes of work)
- Test with an iframe (2 minutes)
- Sleep better knowing you're protected (priceless)
Think of it like locking your car. Takes 2 seconds. Prevents theft. No-brainer! ๐
Resources That Don't Suck ๐
- OWASP Clickjacking Defense - The definitive guide
- MDN: X-Frame-Options - Technical docs
- CSP frame-ancestors - Modern approach
- Laravel FrameGuard - Built-in protection
Got clickjacking stories? Security questions? Hit me up on LinkedIn. As someone active in security communities, I love hearing about novel attack vectors!
Building something secure? Check out my GitHub for more security-focused projects and contributions! ๐ก๏ธ
Now go forth and stop those invisible buttons! ๐ฏโจ
P.S. - After you add those headers, try the iframe test. There's something satisfying about seeing your site refuse to be embedded! ๐