Clickjacking is an easily preventable but sometimes difficult to understand vulnerability. In this article, we’re going to talk about the different ways this vulnerability can be exploited, the associated risk, and how to defend yourself against these types of attacks.
Let’s start with an explanation of how this attack works.
What is Clickjacking?
At its core, clickjacking is an attack that is made possible when the web application does not implement the proper protections against embedding its pages in an iframe. An attacker leverages this behavior to “hijack” a user’s click and trick them into executing an action in their account.
I’ve set up an instance of bWAPP, an intentionally vulnerable web application by ITSec Games, that will allow us to look at a clickjacking attack in practice. Our vulnerable web application is running at “bwapp.raxis.local,” and you’ll see this reflected in the screenshots and sample code below.
Finding a Target
The first thing we’ll need to do is find a page we can target. When looking for a page that might be vulnerable to clickjacking, it should allow for an action that meets three criteria:
- It can be executed in a single click (or very few);
- It affects a logged in user; and
- It could have an undesirable effect on the user or the organization.
It looks like our vulnerable web application has a page that meets all three criteria:
This page allows authenticated users to purchase movie tickets in a single click with a default of 10 tickets. No confirmation is required, and without manipulating the forms on this page, this will result in an unwanted purchase of €150 worth of movie tickets.
This behavior might seem unlikely to exist in the wild, but there are many different types of susceptible pages, from order confirmations like this to functionalities allowing a user to delete entire databases. Some of these susceptible pages may even incorporate parameters in the URL, which can be passed by an attacker. For example, an application might use a URL like the one below to create an order confirmation page dynamically:
Checking for Clickjacking Protections
Now that we have our target page, let’s look at how to use it in an attack.
The first thing we need to determine is whether the target page can be embedded in an iframe. While there are several methods of doing this, the easiest and most reliable by far is to try to embed a target page into an iframe on an attacker-controlled web page and see if it works. Let’s code up the web page now.
As we can see, there’s nothing very complex to this code. It’s a barebones HTML file, which includes an iframe referencing the page we want to embed. Let’s open up our HTML file in our browser and see if this works.
The web page loads into the iframe without issue. We can also see that, since we were already logged in to bWAPP, our session carried over.
Executing the Attack
Now that we have a good idea that the page is vulnerable, all we have left to do is code up a page designed to trick the user into clicking the Confirm button and convince the user into visiting to our malicious page.
To build this malicious page, we’re going to construct an enticing user interface:
Then, we’ll stick the iframe on top and align the Confirm button with the View button. I’ve made the iframe partially transparent in the screenshot below so you can see the multiple layers.
And finally, we’ll make the iframe completely transparent so that the user doesn’t even know that it’s there.
Now, when the user clicks the view button, they’ll actually be clicking the invisible Confirm button and purchasing 10 movie tickets, provided they’re logged in to their account.
Here is the code that I threw together quickly for the purposes of this demo:
Perhaps the trickiest part of this attack is coming up with a method that will get users to go to our malicious page. An attacker might:
- Post a link to the malicious page in social media
- Email a malicious link to a user or group of users
- Embed a link in a QR code
- Attach the malicious HTML file to an email
- Leverage another vulnerability such as cross-site scripting (XSS) or open redirection
When Clickjacking isn’t Clickjacking
Let’s look at a different style of attack that’s popular among some penetration testers. This attack embeds a login page and places input elements on top of the login fields:
The overlaid fields are shown in blue for this article but would normally be completely invisible to the user. When a user fills out this form and submits it, the login request is instead sent to an attacker-controlled server, giving the attacker access to the user’s unencrypted credentials. You can read more about this style of attack here.
But is this type of attack really considered clickjacking? It involves an embedded iframe and overlays, but it is drastically different from our previous example. To determine this, let’s put ourselves into the mindset of an attacker.
As an attacker, our goal here is to capture as many credentials as reliably as possible while evading detection. There are at least two approaches we can take:
- Embed the target login page in a malicious HTML page as shown in the proof of concept, or
- Clone the HTML code of the login page and modify the form to send to a malicious server
These approaches are very similar. Both require hosting the page on a malicious web server, both require misleading the user into visiting a malicious link, and both redirect user input to a malicious server. Both approaches even run the same risk of the malicious web server being reported, blocked, or shut down.
However, when embedding the login page, there are additional factors that the attacker must consider:
- The target login page may already have clickjacking protections in place;
- The target login page could be altered, breaking the malicious page;
- The target login page could implement clickjacking protections, breaking the malicious page;
- The target login page cannot be easily modified by the attacker;
- It’s possible for the target page to determine and report the URL of the malicious page; and
- The malicious page may not look or function correctly if the window is resized
When considering the large number of downsides of using an iframe and overlay elements to conduct this attack, it is extremely unlikely that an attacker would attempt to execute it through clickjacking. Far more likely is that an attacker would simply clone the login page using a myriad of freely available tools designed to do exactly this.
Implementing protections against clickjacking would not stop this attack from occurring. Instead, the attacker would simply clone or recreate the page, which cannot be prevented. The proof of concept itself is also a stretch from the commonly agreed upon definition of clickjacking. For these reasons, Raxis in most cases no longer considers this type of attack to be clickjacking. Instead, we label this as a misconfigured security-related header, which continues to inform the customer of the small but perceptible risk, without blowing what’s essentially a non-exploitable vulnerability out of proportion.
Protecting Against Clickjacking
Despite the complex nature of a clickjacking attack and its reliance on phishing, developers should still take a layered approach to protecting their applications from this type of attack. This can be done by properly configuring two response headers and by properly setting flags on cookies.
For the uninitiated, Content-Security-Policy is a highly versatile header which can offer a large amount of protection to the end user when configured properly. You can generate one at https://report-uri.com/home/generate, and verify your existing one at https://csp-evaluator.withgoogle.com/.
To prevent pages from being embedded in iframes, we can use the frame-ancestors directive. Here’s a few ways this can be properly implemented:
- Content-Security-Policy: frame-ancestors ‘none’ – Prohibits the page from being embedded anywhere
- Content-Security-Policy: frame-ancestors ‘self’ – Allows the page to be embedded on the same domain only (exclusive of subdomains)
- Content-Security-Policy: frame-ancestors https://*.raxis.local https://raxis.local – Allows the page to be embedded on raxis.local and any of its subdomains
X-Frame-Options used to be the primary line of defense against iframe embedding but has since been made obsolete by the frame-ancestors directive. Unless, of course, your users are using any version of Internet Explorer. To cover all our bases and take off this 1.1% of users, we need to add this header for legacy purposes. Configure this header in one of the following ways:
- X-Frame-Options: DENY – Prevent the page from being embedded anywhere
- X-Frame-Options: SAMEORIGIN – Allow the page to be embedded on the same domain only
Cookie Attribute – SameSite
A relatively new cookie attribute, SameSite, can also help protect against clickjacking by restricting the conditions in which cookies (such as a session token or JWT) are sent by the browser.
As of mid-2020, SameSite is set to Lax by default when the attribute is not set in Set-Cookie. This value prevents cookies from being sent in iframes, which essentially breaks any clickjacking attack that relies on the user being logged in.
To use the SameSite attribute as an additional layer of protection against clickjacking, configure the SameSite attribute on your session-related cookies to one of the following values:
- SameSite=Strict – No cross-site cookies, ever
- SameSite=Lax – Cross-site cookies only when the user is navigating to the original site
Since Lax is the default, you can leave this value unconfigured, but it’s a best practice to explicitly configure it as one of the above values, both from a documentation and security standpoint.
You can read more about the SameSite attribute at https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite.
Side Note: Setting SameSite to Strict or Lax also helps defend against cross-site request forgery (CSRF) attacks.