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 clickjacking vulnerable website

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:

The clickjacking attacker's website

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:

Click capture in action

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."

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 attacker_website identifier.
  • The iframe with the vulnerable_website id points to the movie website, but actually, you don't see it on the attacker's page.

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:

Overlapping websites for clickjacking attacks

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.

Prevent 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."

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:

Disabling clickjacking attacks with X-Frame-Options header

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:

  • frame-ancestors 'none'; : This prevents any attempt to include the page within a frame.
  • frame-ancestors https://www.authorized-website.com;: This directive allows you to specify which website is allowed to embed the page in a frame. You can specify multiple URIs.

Auth0 protects its Universal Login page from clickjacking attacks by sending both X-Frame-Options and Content-Security-Policy headers. If both headers are specified, X-Frame-Options takes priority.

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."

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 sameSite property has been recently introduced, so old browsers may not support it.

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 provides a platform to authenticate, authorize, and secure access for applications, devices, and users. Security and application teams rely on Auth0's simplicity, extensibility, and expertise to make identity work for everyone. Safeguarding more than 4.5 billion login transactions each month, Auth0 secures identities so innovators can innovate, and empowers global enterprises to deliver trusted, superior digital experiences to their customers around the world.

For more information, visit https://auth0.com or follow @auth0 on Twitter.

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.