Buttons are everywhere. Elevator buttons, machinery buttons, and even "Nuclear Button" that sits on the President's office desk. But are you always sure the button you push really performs what you want it to do?

<iframe> is a HTML element that lets a web page embed another web page. Here I'm embedding a fake banking website (shamelessly stolen from @lcamtuf with slight modification).

The scenario here is after clicking the "Transfer everything" button, all your funds will be transferred to me. Of course, clicking it won't actually do anything. But will you click it?

Now, I'm presenting you another button. It doesn't have much to say except "harmless", and I challenge you to click it.

Harmless button

Uh-oh looks like all your moneyz are belong to me. Don't worry, it's just virtual money worth nothing like Bitcoin. Anyway what's happened? CSS has a property called position that allows an element to sit on top of another element. In addition, the property pointer-events allows click events to passthrough an element, so that the click is actually registered on the element underneath. When two combined, I can stack a fake button on top of the actual button and trick you to click it. You can use the "Behind the scene" button to have a visual understanding of how it works. This is an often overlooked and misunderstood web application vulnerability called Clickjacking.

Intended Use Case

So <iframe> is dangerous, pointer-events is lethal and we should remove them? Yes and no. Things wouldn't exist if there were no use cases, and same as <iframe>. Online advertising heavily relies on <iframe>. More importantly, web widgets use <iframe> too. Facebook like button and Facebook comments plugin are prime examples of such kind.

Here's another harmless button and I challenge you to click it.

Click Me

If you have logged in Facebook, then what you just did was to like my blog post (which I appreciate) without realizing it. This time, opacity is used instead of pointer-events. opacity is a CSS property that controls the opacity of an element. Therefore, I can simply stack the transparent Facebook like button on top of another button visible to you to achieve the same Clickjacking effect, making the remediation of removing pointer-events useless. And by the way, this is a Blackhat SEO technique called Likejacking which exploits Clickjacking to gain organic likes.

Click Me

People have been exploiting Clickjacking with web widgets. Many social media sites were actually vulnerable to this. For example, you could use Clickjacking to gain followers with Twitter follow button, and a more recent issue on LinkedIn AutoFill button that leaks visitor's infomation to third-party websites. Sites have started fixing it by requiring addition user interactions, for example, open a new window for users to confirm an action. In fact, Likejacking probably no longer works nowadays. If you try to click the above Facebook like button, it may turn into a "Confirm" button which requires you to click again after you click it. Essentially, they now use an algorithm to determine if an embedding site is trustworthy and hence the number of additional user interactions. Ultimately this is a trade-off between usability and security.

Framebuster

Apparently killing <iframe> and CSS properties is not a good idea. What we really need is the ability for websites to forbid other websites from embedding them. Back in the day, people developed a technique called Framebusting which uses JavaScript to check if a website is being embedded by another site. Since window.top always points to the outermost frame, by comparing it to window.self it is possible to determine if the embedding site is not itself. Here was the most commonly used snippet:

if (top != self)
  top.location = self.location

However, JavaScript based framebusters have many flaws. For example, onBeforeUnload can abort navigation, XSS Filter can disable partial JavaScript execution as mentioned in my previous blog post, and the sandbox attribute of <iframe> can even completely disable JavaScript of the embedded site. The research Busting Frame Busting:
a Study of Clickjacking Vulnerabilities on Popular Sites
further shows that JavaScript based framebusters are fragile.

Fortunately, browsers started supporting the HTTP response header X-Frame-Options which allows websites to control framing behavior. X-Frame-Options: DENY indicates the site doesn't want anyone to frame it, X-Frame-Options: SAMEORIGIN indicates that only web pages on the same origin can frame it, and X-Frame-Options: ALLOW-FROM indicates only the specified site can frame it. The frame-ancestors directive of Content Security Policy Level 2 later attempts to deprecate X-Frame-Options.

However, X-Frame-Options: SAMEORIGIN has a serious flaw. It only compares the embedded site against the top frame.

XFO sameorigin

You may wonder, yeah as long as the top frame is same origin why does it matter? Keep in mind some websites explicitly and implicitly allow custom iframe. In fact, Twitter Player Card was vulnerable to it. When you tweet a link, Twitter will fetch the link and read its meta. If the Open Graph attributes specify a player URL, Twitter will embed the URL in an iframe directly in your timeline. I could embed a sensitive page on Twitter on my "player", say OAuth authorization, and mask a fake button on top of the authorize button.

Twitter Player Card Clickjacking

In the actual exploit I used onblur on the main window to detect any click events since clicking on the iframe will trigger a focus event on the iframe, then I delivered a feedback (play video) to make the Clickjacking less obvious. You can learn more vulnerable examples from my presentations.

At the time of writing, only Chrome and Firefox have fixed the issue by making the check against all frame ancestors, which is compliant to CSP's frame-ancestors 'self'.

ChromeFirefoxSafariEdgeInternet Explorer
X-Frame-Options: SAMEORIGIN🐛🐛🐛
Content-Security-Policy: frame-ancestors 'self'

Back to the topic...

I know what you're thinking. What does it have to do with "Google YOLO"? I have a surprise if you browse this page with a modern browser in PC :)


Google YOLO

YOLO (You Only Login Once) is a web widget for "One-tap sign-up and auto sign-in on websites" by Google. You embed Google provided iframe on your websites and your users can authenticate with their Google account in one simple click. Ironically, "web widgets" and "one click" is indeed YOLO (You Only Live Once).

Remember the cookie consent button you clicked at the very beginning? That's right, it was a Clickjacking attempt :).

When you try to login with Google YOLO, the following data will be transmitted:

{
  "id": "filedescriptor@gmail.com",
  "authMethod": "https://accounts.google.com",
  "authDomain": "https://blog.innerht.ml",
  "displayName": "file descriptor",
  "profilePicture": "https://lh6.googleusercontent.com/-enDr8I8LBzQ/AAAAAAAAAAI/AAAAAAAAAH0/bANw2nF8nWI/photo.jpg?sz=96",
  "idToken": "redacted"
}

Exploiting Clickjacking on Google YOLO allows visitors' name, profile picture and email address to be leaked. That's right, I can even know your email address. :). Click here if you want to see behind the sense (make sure you have logged in Google with a modern browser, PC preferably). This was silently "fixed" by Google, see the end of article.

I've reported this privacy issue to Google and they refused to fix it as they consider it an "intended behavior".

Thanks for your bug report and research to keep our users secure! We've investigated your submission and made the decision not to track it as a security bug.

The login widget has to be frameable for it to work. I'm not sure how we could fix this to prevent this problem, but thanks for the report!

This report will unfortunately not be accepted for our VRP. Only first reports of technical security vulnerabilities that substantially affect the confidentiality or integrity of our users' data are in scope, and we feel the issue you mentioned does not meet that bar :(

Moral of the story

Don't push random buttons

Actually don't even click anything. Malicious websites can simply track your cursor's position and change the invisible button/iframe's position accordingly. So even if you make a click by mistake you will be forced to click on something else.

Click me

There's no reliable way to prevent Clickjacking, though mitigation can be done on both ends:

  • Sites need to proactively deploy X-Frame-Options or Content-Security-Policy header.
  • Web widget providers need to make sure sufficient user interactions are required
  • Users can consider disabling third-party cookies
  • Use browser profiles

Last but not least, dare you click this button :)?

???

Update

Shortly after thie article was published, Google silently prevented my domain from using the API:

The client origin is not permitted to use this API.

Welp.

Demo