Let's imagine our target is a social media site: https://socialapp.example.com.
You browse the application and look for actions that change the state of your account or data and only require a single click (no text input, drag-and-drop, etc.).
Prime candidates on socialapp.example.com:
- Profile Actions:
https://socialapp.example.com/profile/follow(Button to follow a user)https://socialapp.example.com/profile/unfollow(Button to unfollow a user)https://socialapp.example.com/profile/block(Button to block a user)
- Post Actions:
https://socialapp.example.com/post/like(POST request when liking a post)https://socialapp.example.com/post/delete(Button to delete your own post)
- Account Settings:
https://socialapp.example.com/settings/deactivate(Button to deactivate account)https://socialapp.example.com/email/change(Form with a "Save Changes" button)
Best Target for Demo: The like or follow functionality. It's low-risk for testing but proves the vulnerability exists.
You need to check if the page containing the action button can be loaded inside an iframe. Open your browser's Developer Tools (F12) and inspect the network traffic when loading one of your target pages, like https://socialapp.example.com/profile/follow.
Look for these protective headers in the Response Headers:
-
X-Frame-Options: This is the classic defense.X-Frame-Options: DENY→ Protected. Cannot be framed at all.X-Frame-Options: SAMEORIGIN→ Protected. Can only be framed by pages on the same domain.- If this header is missing or has an
ALLOW-FROMvalue (deprecated and poorly supported), it might be vulnerable.
-
Content-Security-Policy(CSP): The modern defense.Content-Security-Policy: frame-ancestors 'none'→ Protected. Equivalent toDENY.Content-Security-Policy: frame-ancestors 'self'→ Protected. Equivalent toSAMEORIGIN.Content-Security-Policy: frame-ancestors https://trusted.com→ Protected (unless you own trusted.com).- If the
frame-ancestorsdirective is missing entirely, or is set to*, the site is vulnerable.
-
Session Cookie
SameSiteAttribute:- Check the
Set-Cookieheader for your session cookie. SameSite=LaxorSameSite=Strict→ Partially Protected. The cookie won't be sent in a cross-site POST request triggered by an iframe, which breaks many state-changing actions.SameSite=Noneor the attribute is absent → Vulnerable. The cookie will be sent with the request from the iframe.
- Check the
Result for our example: Let's say the page https://socialapp.example.com/profile/follow has no X-Frame-Options header, no frame-ancestors directive in its CSP, and the session cookie is set without a SameSite attribute. This means it's a prime candidate for clickjacking!
Create a simple HTML file on your local machine. This file will act as the malicious attacker page.
clickjack-test.html
<!DOCTYPE html>
<html>
<head>
<title>Win a Free iPhone!</title>
<style>
/* The iframe is made transparent and positioned over the button */
iframe {
position: absolute;
top: 100px;
left: 100px;
width: 400px;
height: 400px;
opacity: 0.5; /* Start semi-transparent for testing */
z-index: 2;
}
/* A fake button placed UNDER the iframe */
#fakeButton {
position: absolute;
top: 250px;
left: 150px;
width: 100px;
height: 50px;
z-index: 1;
background-color: green;
color: white;
text-align: center;
line-height: 50px;
cursor: pointer;
}
</style>
</head>
<body>
<h1>Congratulations! You've Won!</h1>
<p>Click the GREEN button below to claim your FREE iPhone!</p>
<!-- This is the fake, enticing button -->
<div id="fakeButton">CLAIM NOW!</div>
<!-- This iframe loads the target action page -->
<iframe src="https://socialapp.example.com/profile/follow?targetUserId=attacker123"></iframe>
</body>
</html>- Open this file in your browser.
- You should see the target page (
/profile/follow) loaded inside the iframe, layered over the green "CLAIM NOW!" button. - Test the framing: If you can see the content of
socialapp.example.cominside the iframe, the framing was successful! The site is vulnerable. - Now, set the iframe's
opacity: 0.0;to make it completely invisible.
- Log into
socialapp.example.comin your browser. - Open your
clickjack-test.htmlfile in the same browser (the session cookies will be sent with the iframe request). - Click on the green "CLAIM NOW!" button.
- Since the invisible iframe is positioned exactly over the button, you are actually clicking the "Follow" button inside the iframe.
- Verification: Navigate to your "Following" list on
socialapp.example.com. You should now be following the userattacker123. The attack was successful!
-
Delivery: An attacker wouldn't use a local file. They would:
- Host the malicious HTML on a server they control at a URL like
https://free-iphone-giveaway.com. - Send phishing emails or social media messages linking to this page.
- The page would be styled to look like a legitimate giveaway or a required security check.
- Host the malicious HTML on a server they control at a URL like
-
Larger Impact:
- For our example: An attacker could mass-generate followers, automatically block a user's friends, or silently like/dislike posts to manipulate algorithms.
- For more critical actions: Imagine the target was
https://socialapp.example.com/settings/deactivateorhttps://bank.example.com/transfer?amount=1000&to=attacker. The impact escalates to account takeover, financial loss, or complete reputation damage.
Impact:
An attacker can craft a malicious webpage that tricks a logged-in user into performing actions on socialapp.example.com without their knowledge or consent. This could be used to:
- Force users to follow malicious accounts.
- Force users to block their own friends.
- Like/Dislike posts to manipulate content visibility.
- If critical pages like account deletion are also vulnerable, it could lead to account takeover.
Remediation:
Implement the Content-Security-Policy header with a restrictive frame-ancestors directive on all pages, especially those that perform state-changing actions.
- Recommended:
Content-Security-Policy: frame-ancestors 'none'; - If framing by the same origin is needed for some features:
Content-Security-Policy: frame-ancestors 'self';