This is the third and final part in this three-part series, Three C-Words of Web Application Security. I wrote a sort of prologue back in April, called A Brief Evolution of Web Apps, just to set the scene for those less versed in web application history. The first part, which was on CORS (Cross-Origin Resource Sharing), was published in July. The second part, on CSRF (Cross-Site Request Forgery) was published near the end of October. This final post in the series will address Clickjacking, and, in particular, the role it can play in CSRF exploitation.
Clickjacking is an example of UI (user interface) redress attack. Meaning the attacker can trick a user into interacting with the UI for a vulnerable application by redressing it, or making it look like something completely different. To be specific, when we say “interacting”, we’re typically talking about having the user click on something.
A Primer on iFrames
The main prerequisite for this is the ability to show the vulnerable page inside of an iframe element. An iframe is like a rectangular element on the page that can display another webpage within it. Here’s what that normally looks like:
This is my site
<iframe src="https://secureideas.com/" style="height:200px;width:400px;">
^ Up there is the Secure Ideas webpage.
…produces a page that looks like this:
A page is shown within a page, sort of like picture-in-picture mode on a television. In the context of iframes, same-origin policy, as discussed in Part 1 of this series, still exists. What this means is that the parent page, whose markup is shown above, has no special access to read or manipulate the document inside the iframe. All it has done is instructed the browser to load and display that page within this 400 by 200 pixel frame.
When Is A Site Actually Susceptible?
The ability to frame a page like this is normal behavior, and does not necessarily lead to a serious vulnerability unless a specific context is present. In fact, it’s not terribly common to have a really viable scenario for a meaningful clickjacking attack. That’s exactly why it’s important to understand it as a penetration tester or a developer, so that you can recognize when such a scenario exists.
So let us assume the prerequisite is met: we have a whole site that is frameable; how do we know the context is there for an actual clickjacking exploit? First of all, we need to be able to blindly mutate data or application state in some way with a fairly simple user interaction such as a click. Essentially, this is the same criteria as a CSRF attack, except that for some reason (specific examples ahead) CSRF is not a viable option. If a pure CSRF attack can work, it will almost always be a better option, as it reduces the need for user interaction. When will Clickjacking work, but CSRF will not? Let’s consider two specific scenarios:
We have an action that can be initiated by CSRF, but it uses a confirmation page. Let’s imagine it’s specifically an Add User function, and it follows the flow below:
- The (CSRF Susceptible) Create User form is submitted.
- The server sets up the user and generates some sort of unique Transaction ID that is passed back to the client as part of the confirmation page.
- The user clicks Confirm or some other affirmative, to finish creating the user.
If we try to exploit this with CSRF, we can successfully submit that Create User form and have the transaction created, but that second request needs to include a Transaction ID. Barring an entropy that allows us to guess it, we don’t know it. Since the context of CSRF is blind, we will not be able to read the response. CSRF doesn’t get the job done here, however, if we submit the form via an iframe, the confirmation page will load in that iframe. We still can’t read it, but if the target user were to simply click that Confirm button for us, they would complete the transaction.
Let’s consider the same basic premise – a Create User form, but this time there are three, crucially different characteristics from the previous scenario. The first is that it no longer has a confirmation page; the Create User form directly creates the user. The second is that there’s now a CSRF token on the page. This contains a value that an attacker would not know, therefore CSRF is defeated. The third difference is that the form can somehow be pre-populated, such as by providing query string parameters or route parameters, including client-side hash routing parameters. Instead of sending a request to tiny.si/createUser, we can request tiny.si/createUser?role=admin&email=mic%40secureideas%2Ecom and the form fields will be prepopulated. This was common in legacy apps using the Post-Redirect-Get pattern, but still sometimes exists on modern apps where functionality calls for pre-filling forms. And again, we can send this request via an iframe in a target user’s browser, leveraging that user’s session. If that form were submitted, it would have the valid CSRF token since it’s a valid page. But populating the form in the iframe is not the same as submitting it. Same origin policy prevents our malicious page from automatically submitting the form loading inside the iframe, but if the target user were to simply click that Submit button for us, they would submit the form, with their authenticated session, with a valid CSRF token. Our user would be created for us.
What do they have in common?
Let us review the relevant characteristics that are common to both scenarios:
- CSRF attacks will not quite work, at least on their own.
- Some sensitive action can be done by an authenticated user.
- We can, via a cross-site request or via the URL, supply the parameters we want for the operation, and valid parameters for all required input fields.
- The only interaction that we need from a user is to click an element on the page.
A few more assumptions
To make for a realistic attack, let’s assume two more things:
- The button that that we want the user to click is in a predictable location on the page.
- The attacker is able to view the page in some context, for example from his/her own account.
So how does the actual redress happen? The attacker sets up a malicious page, like this one:
This particular example uses a pretty crude ruse, but let’s walk through what’s going on. There’s a form, just above the middle of the markup, with an action of InitialCsrfUrl; this would actually be the target endpoint on a CSRF susceptible app server, such as https://tiny.si/createUser in our scenarios. There’s a placeholder comment inside the form, but it would really instead be hidden input elements for the request parameters in there. This part is much like the CSRF proof-of-concept you would generate from the CSRF PoC Generator that Burp Suite Pro has under the engagement tools. One difference from the typical generate PoC is that target attribute which has a value of myframe. This has the effect of submitting the form through the iframe (with a name matching the target) rather than redirecting the top-level window. The big picture for this part is that when the form is submitted, a CSRF attack will be executed, and the resulting page will be displayed in the hidden iframe. Now, let’s look at the script block near the bottom of the page. Starting with that last line, the setTimeout triggering the form.submit() is the part that actually executes the CSRF. When the iframe loads the page, that inline event handler (frame.onload) function in the middle will show the hidden iframe, and scroll it to a specific position. There’s a wrapper div around the iframe that hides all of it except a 40px by 80px viewing window, and that scroll position should put the target button inside that viewing window. Finally, the div with the id attribute of redress is carefully positioned on top of the wrapper viewing window, to hide the button underneath.
Relating that to our scenarios, it works as follows: using some sort of social engineering, the attacker can target a user to browse to the malicious page. Behind the scenes, this loads the confirmation page in an iframe element. Specifically, in scenario 1 it will send a cross-site post with a target of the iframe, meaning the response will be received in the iframe. In scenario 2, the malicious page will simply have an iframe element with the parameters included in the querystring. In both cases, the browser will automatically attach the target user’s cookies and/or auth headers when that user’s browser issues the request. The result, again in both cases, is that the page loaded in the iframe has a button that the attacker needs the target user to click. This is the confirmation page in scenario 1, and the filled Create User form in scenario 2. If the target clicks the button, the sensitive action in the target app (create user, in our two scenarios) will be completed successfully – again using the target user’s active session.
The iframe is sized so that it can be hidden behind our Click Here button, and scrolled so that the Submit or Confirm button is in-frame.
And that’s all there is to it, really. If the user clicks the Click Here button, they will actually be interacting with the iframe underneath, and will submit the form (again, including their cookies/auth headers) and complete the transaction.
Defending Against Clickjacking
The single best defense against clickjacking is to not let other sites put it inside an iframe. The x-frame-options HTTP response header has support on most major browsers since approximately 2013 with some adopting it much earlier. As a point of reference, IE8 supports it. x-frame-options can be set to one of several values, the standard ones that will prevent Clickjacking being either deny or sameorigin. There is also an allow-from <uri> value that would work as long as the attacker cannot host arbitrary content on the supplied URI. The problem with allow-from is that it’s not fully supported across browsers.
A somewhat newer defensive measure is a Content Security Policy (CSP) with a frame-ancestors directive. Values of frame-ancestors ‘none’ and frame-ancestors ‘self’ in the CSP are approximate analogs to x-frame-options: deny and x-frame-options: sameorigin respectively. While it is certainly worth including the directive if you have a CSP, I would recommend also including the x-frame-options header for its legacy browser support.
Incidentally, CSP is also our secret Fourth C-Word of Application Security. While it’s typically difficult to retrofit onto mature apps, I recommend using a CSP on any greenfield web application builds today.