Clickjacking attacks rely on visual tricks to get website visitors to click on user interface elements that will perform actions on another website. This article shows you how a clickjacking attack works in practice and how you can prevent them.
What Is Clickjacking?
The goal of a clickjacking attack is to trick unsuspecting website visitors into performing actions on another website (the target website). For example, a user may be attracted by a website that promises them an exceptional prize. When the user clicks a button to accept the prize, their click is instead used to purchase an item on an e-commerce website. Typically this attack is performed by hiding the target website's UI and arranging the visible UI so that the user isn't aware of clicking the target website. Due to this UI arrangement, this kind of attack is also known as UI redressing or UI redress attack.
Because of this deception, the user unwittingly performs operations like transferring money, purchasing products, downloading malware, give them like on a social network, and so on. But how does it work? Read on and you'll find out
Types of Clickjacking Attacks
Based on the nature of the specific operation, the attack may assume different names. Consider, for example, the following variants:
- Likejacking: This kind of attack aims to grab users' clicks and redirect them to "likes" on a Facebook page or other social media networks.
- Cookiejacking: In this case, the user is led to interact with a UI element, for example, via drag and drop, and to provide the attacker with cookies stored on their browser. This way, the attacker could be able to perform actions on the target website on behalf of the user.
- Filejacking: With this type of attack, the user allows the attacker to access their local file system and take files.
- Cursorjacking: This technique changes the cursor position to a different place from where the user perceives it. This way, the user believes they are making an action while they are actually making another one.
- Password manager attacks: This type of attack aims to deceive password managers to take advantage of their auto-fill functionality.
Those are just a few of many possible other clickjacking variants. Even if many variants exist, keep in mind that the basic principle they rely on is the same: capture a user action through a UI trick.
Clickjacking in Action
To understand the details behind a clickjacking attack, let's take a look at how it may happen in practice. This will help you to learn its behavior and figure out a strategy to prevent it.
To run the sample application you are going to download, you just need Node.js installed on your machine. However, the principles behind the clickjacking vulnerability and the prevention strategies are independent of the specific programming language or framework.
Set up the environment
Let's start by cloning the sample app from the GitHub repository accompanying this article. Run the following command on your machine:
git clone https://github.com/auth0-blog/clickjacking-sample-app.git
Once the download is complete, install the project's dependencies by moving in the project's folder and running this command:
npm install
Now you can launch the vulnerable website of the sample project by typing the following:
npm start
Finally, open your browser and navigate to the http://localhost:3000 address. You should see the following page:
The project implements a specific movie page of a fictitious movie streaming website. Besides allowing you to stream a movie, you can also buy the related DVD. However, you need an authenticated session to play and buy a movie. To make things simple, this project doesn't implement an actual authentication process. You can simulate an authenticated session by clicking the valid session link within the message at the bottom of the page. After getting the simulated user session, that message disappears.
Launch the clickjacking attack
Once the movie website is running, you are going to set up the clickjacking attack to it. You will be running another website, the attacker's website, whose code will grab your click and redirect it to the movie website without you realizing it.
So, let's run the attacker's website by running the following command in a terminal window:
node attacker-server.js
Then, open a new tab or a new instance of your browser and navigate to http://localhost:4000. You should see the following page:
This page promises you an amazing holiday by simply clicking the Accept the prize! button. This appears totally unrelated to the movie website. However, if you click the button, you are actually buying the DVD on the movie website. You can verify this by opening the developer tool of your browser and analyzing the network traffic while clicking the button. This is an example of this tracking in Chrome DevTools:
You may notice an HTTP POST request to the
http://localhost:3000/purchase
endpoint of the movie website.What is the relationship between this website and the movie website? You will be discovering this in a moment.
“A clickjacking attack aims to capture a user action through a UI trick.”
Tweet This
Anatomy of the attack
To understand what's happened, let's take a look at the attacker's website page. Move to the
views
folder and open the index.ejs
page. The body of the page looks as follows:<!-- views/index.ejs --> <html lang=en> <!-- ...existing markup... --> <body> <div id="attacker_website"> <h1>You Are The Winner!!!</h1> <h2>You won an awesome tropics holiday!</h2> <img alt="Tropical Holiday" src="images/tropical-holiday.jpg"/> <h2>Accept it by clicking the button below.</h2> <button type="submit">Accept the prize!</button> </div> <iframe id="vulnerable_website" src='http://localhost:3000'> </iframe> </body> </html>
Its content is an EJS template with two main elements:
- The visible part of the page is defined by the div with the
identifier.attacker_website
- The iframe with the
vulnerable_website
points to the movie website, but actually, you don't see it on the attacker's page.id
The trick that connects the two websites is performed by the CSS rules defining the position and visibility of those elements. Let's take a look at them:
<!-- views/index.ejs --> <html lang=en> <head> <!-- ...existing markup... --> <style> #vulnerable_website { position:relative; opacity:0.0; width:800px; height:900px; top:75px; left: -95px; z-index:2; padding-left:80px; } #attacker_website { position:absolute; z-index:1; } #attacker_website button { margin-left:100px; } </style> </head> <!-- ...existing markup... --> </html>
As you can see, the
vulnerable_website
iframe has assigned the 0.0
value for its opacity, which makes it transparent. In addition, it is positioned so that it overlaps the attacker_website
div. You can see how the iframe overlaps the div by adjusting the opacity value of the iframe to 0.3
. After changing that value, restart the attacker's website. You should see the attacker's page as shown in the following picture:You see that the Accept the prize! button overlaps the Buy DVD button on the movie website.
The
z-index
property completes the job: the invisible iframe is over the attacker's div. So, users think they are clicking the Accept the prize! button, but they are actually hitting the Buy DVD button.Now that you know how a clickjacking attack works, it should be clear why this technique is also known as UI redressing.
Note that, to make this attack successful, you need to have a valid session on the movie website. However, the attack itself is not about exploiting session cookies. The attack can be performed even against a website that doesn't require any authentication.
Differences with CSRF
The mechanics behind a clickjacking attack may look similar to a CSRF attack, where the attacker sends a request to the target server by using your active session. However, they are quite different.
In fact, in the CSRF case, the attacker builds an HTTP request and exploits the user session to send it to the server. In the clickjacking case, the user is directly interacting with the target website. No request is built by the attacker. The request sent to the target server is a legitimate user request. Check out this blog post to learn more about CSRF attacks and how to prevent them.
Want to learn more about Credential Stuffing Attacks?
Download the whitepaperPrevent Clickjacking Attacks
Now you know how clickjacking attacks work. Let's discuss how you can prevent them and make your website safer.
Even if the application example provided in this article is a traditional web application, consider that the core of the attack is the ability to include a website or application within an iframe. This means that a clickjacking attack may affect any type of application independently of the technology or framework used to build it. So, not only regular web apps, but also React, Angular, and other apps are vulnerable.
The same applies to the server side of the application.
“Learn how to protect your web applications from clickjacking attacks.”
Tweet This
Client-side defenses
Since clickjacking attacks leverage iframes, you may think that applying some sort of client-side defense that prevent your website from being loaded in iframes can protect it. This technique, known as frame-busting, can be implemented with a few lines of JavaScript.
Consider adding the following script in the head section of the vulnerable website's page:
<!-- templates/index.ejs --> <!DOCTYPE html> <html lang=en> <head> <meta charset=utf-8> <meta name="viewport" content="width=device-width, initial-scale=1.0"/> <link rel="stylesheet" type="text/css" href="css/tacit-css.min.css"/> <title>The Vulnerable Movie Center</title> <!-- 👇 new code --> <script> if (top != window) { top.location = window.location; } </script> <!-- ...existing markup... --> </html>
If the page of the vulnerable website is not within the browser's topmost window, it is reloaded in the top window. In practice, this script replaces the attacker's page with the hidden page allowing the user to unmask the attack.
It looks like a good solution to the problem. However, there are a few issues with this approach. For example, some browsers or browser add-ons may block the automatic reloading. Also, the attacker may build their page so that it neutralizes that defense. For example, the attacker can simply add the following script to their page:
<!-- views/index.ejs --> <html lang=en> <head> <!-- ...existing markup... --> </head> <body> <!-- 👇 new code --> <script> window.onbeforeunload = function() { return false; }; </script> <!-- ...existing markup... --> </body> </html>
By handling the
onbeforeunload
event, the attacker attempts to disable the current page dismissal. This approach could not work in some browsers since it may require user confirmation. But the attacker has other options. For example, they can use the iframe's sandbox
attribute, as shown below:<!-- views/index.ejs --> <html lang=en> <head> <!-- ...existing markup... --> </head> <body> <!-- ...existing markup... --> <!-- 👇 changed code --> <iframe id="vulnerable_website" src='http://localhost:3000' sandbox="allow-scripts allow-forms allow-same-origin"> </iframe> </body> </html>
In this case, the attacker specifies that the iframe content is allowed to execute scripts (
allow-scripts
), to submit forms (allow-forms
), and to be treated as being from the same origin (allow-same-origin
). No other permission is specified, so the iframe content cannot replace the top-level content, as the allow-top-navigation
value would have allowed.In other words, the client-side defenses to clickjacking attacks are easy to get around and are not recommended except to mitigate the problem in legacy browsers.
If you want to try this client-side defense and the attacker's related neutralization, you can download the adapted project with the following command:
git clone -b client-side-defenses https://github.com/auth0-blog/clickjacking-sample-app.git
Consider that, besides the approach shown here, there are other similar techniques based on JavaScript and HTML that try to prevent clickjacking attacks. However, in general, client-side attempts to block clickjacking attacks usually don't work well since they are easy to get around.
Using the X-Frame-Options header
A better approach to prevent clickjacking attacks is to ask the browser to block any attempt to load your website within an iframe. You can do it by sending the
X-Frame-Options
HTTP header.Start from the original sample project by following the instructions given in the Set up the environment section. Then, edit the
server.js
file as shown below:// server.js const express = require('express'); const session = require('express-session'); const bodyParser = require('body-parser'); const port = 3000; const app = express(); app.set('views', './templates'); app.set('view engine', 'ejs'); //👇 new code app.use(function(req, res, next) { res.setHeader('X-Frame-Options', 'sameorigin'); next(); }); // ...existing code... app.listen(port, () => console.log(`The server is listening at http://localhost:${port}`));
You configured an Express middleware that adds the
X-Frame-Options
HTTP header to each response for the browser. The value associated with this response header is sameorigin
, which tells the browser that only pages of the same website are allowed to include that page within an iframe.With this restriction, an attacker will not be able to use the technique shown before to capture users' clicks. You can verify that this defense works by changing the iframe opacity in the attacker's page:
<!-- views/index.ejs --> <html lang=en> <head> <!-- ...existing markup... --> <style> #vulnerable_website { position:relative; opacity:0.3; //👈 changed code width:800px; height:900px; top:75px; left: -95px; z-index:2; padding-left:80px; } #attacker_website { position:absolute; z-index:1; } #attacker_website button { margin-left:100px; } </style> </head> <!-- ...existing markup... --> </html>
Once you apply this change, restart the attacker's website. You should see something similar to the following picture:
This time the iframe content is blocked.
An alternative value for the
X-Frame-Options
header is deny
, which prevents any attempt to put the page within a frame.To try this approach, you can download the sample app project variation with the following command:
git clone -b x-frame-options https://github.com/auth0-blog/clickjacking-sample-app.git
Using CSP
Major browsers support the
X-Frame-Options
header. However, it has never been standardized, so there might be browsers not supporting it. An alternative standard approach to prevent clickjacking attacks is by using specific Content Security Policy (CSP) directives.For example, after restoring the sample app project to its original state, change the content of the
server.js
file as follows:// server.js const express = require('express'); const session = require('express-session'); const bodyParser = require('body-parser'); const port = 3000; const app = express(); app.set('views', './templates'); app.set('view engine', 'ejs'); //👇 new code app.use(function(req, res, next) { res.setHeader("Content-Security-Policy", "frame-ancestors 'self';"); next(); }); // ...existing code... app.listen(port, () => console.log(`The server is listening at http://localhost:${port}`));
In this case, you attach the
Content-Security-Policy
header with the frame-ancestors 'self';
value to each outgoing response. This CSP directive allows you to get the same result as the X-Frame-Options
header with the sameorigin
value.Alternative values to control iframe embedding through the
Content-Security-Policy
header are:
: This prevents any attempt to include the page within a frame.frame-ancestors 'none';
: This directive allows you to specify which website is allowed to embed the page in a frame. You can specify multiple URIs.frame-ancestors https://www.authorized-website.com;
Auth0 protects its Universal Login page from clickjacking attacks by sending both
andX-Frame-Options
headers. If both headers are specified,Content-Security-Policy
is ignored.X-Frame-Options
To test the CSP approach to defend the sample app from clickjacking, download the project by running this command:
git clone -b content-security-policy https://github.com/auth0-blog/clickjacking-sample-app.git
“Use Content Security Policy to prevent clickjacking attacks.”
Tweet This
Using cookie's sameSite origin
If your web application is vulnerable to clickjacking due to session cookies, like in the sample app that comes with this article, you can protect it by leveraging the
sameSite
property of cookies. In this case, the defense is not based on breaking the iframe behavior but on preventing the session from being valid when the website is within an iframe. Consider that this approach doesn't help you when your website actions don't rely on an active session.The
property has been recently introduced, so old browsers may not support it.sameSite
Let's take a look at how you can apply this approach. Restore the original project as described in the Set up the environment section and change the content of the
server.js
file as in the following code snippet:// server.js // ...existing code... app.use(express.static('public')); app.use(session({ secret: 'my-secret', resave: true, saveUninitialized: true, cookie: { httpOnly: true, sameSite: 'strict' //👈 new code } })); app.use(bodyParser.urlencoded({ extended: true })); // ...existing code... app.listen(port, () => console.log(`The server is listening at http://localhost:${port}`));
You simply added the
sameSite
property with the 'strict'
value to the session cookie. This value tells the browser not to send the session cookie when the request comes from a different domain.To verify that the user can't finalize the purchase through the attacker's website, you need to access the attacker's website via the http://127.0.0.1:4000 address. This is necessary because you are using the same domain name (localhost) for both the vulnerable and the attacker websites, and cookies are shared independently of the port. Of course, in production, you will not have this problem.
To download the project implementing this clickjacking defense, run the following command:
git clone -b same-site-cookie https://github.com/auth0-blog/clickjacking-sample-app.git
About Auth0
Auth0 by Okta takes a modern approach to customer identity and enables organizations to provide secure access to any application, for any user. Auth0 is a highly customizable platform that is as simple as development teams want, and as flexible as they need. Safeguarding billions of login transactions each month, Auth0 delivers convenience, privacy, and security so customers can focus on innovation. For more information, visit https://auth0.com.
Conclusion
This article guided you in analyzing how a clickjacking attack works and how you can protect your web application by applying different approaches. Each of the proposed strategies has benefits and drawbacks. As the primary concern of security defense is not to be bypassed, the best approach is to combine those approaches so that if one fails, say, due to lack of browser support, the other may succeed.
For a more in-depth discussion about clickjacking prevention, you can take a look at the OWASP cheat sheet.
About the author
Andrea Chiarelli
Principal Developer Advocate
I have over 20 years of experience as a software engineer and technical author. Throughout my career, I've used several programming languages and technologies for the projects I was involved in, ranging from C# to JavaScript, ASP.NET to Node.js, Angular to React, SOAP to REST APIs, etc.
In the last few years, I've been focusing on simplifying the developer experience with Identity and related topics, especially in the .NET ecosystem.