Background

While auditing the Mattermost desktop application as part of my general Mattermost review, I made the somewhat surprising discovery that it was written in Node.js using the Electron framework. For those who are unfamiliar with Node.js, it is essentially an application runtime for running JavaScript on the server, similar to the ones of Java and PHP. While the wisdom of running JavaScript on the server can be debated (from a security standpoint at least), running it on a desktop outside of a real browser poses its own risks. When JavaScript executes in a browser, its capabilities for interacting with the underlying system is restricted by the APIs exposed by the browser. These restrictions does not apply to Node.js, as it is not a browser, which means that Node.js can for example invoke a shell, access the file system, etc.



The Mattermost desktop application

The Mattermost desktop application is in many ways simply a Node.js application pretending to be a browser. It interacts with the Mattermost backend in almost exactly the same way as the web application, the only differences being the User-Agent and that the desktop app (wisely) does not load any HTML or JavaScript over the network. However, even though the desktop app in a lot of ways is a browser, the Mattermost team does not want it to be used as one which is why the following code exists.

When a URL that has been posted to a channel is clicked within the desktop client, this code checks whether the URL is an internal or external one. If the URL is internal, it will open within the Mattermost desktop application, and if it is external it will invoke a shell and open it using the system’s default browser.

The somewhat dangerous assumption here is that untrusted HTML will never be loaded from the domain of the Mattermost instance, which of course also means that if this can be accomplished, code execution will be trivial. Fortunately (or not, depending on how you see it), I already had that capability in the shape of a stored XSS that I found earlier and that is described here.

Getting code execution

The only thing left to get code execution is to upload an HTML file containing some JavaScript to invoke a shell, such as:

<script>

var calc = ‘C:/Windows/System32/calc.exe’;

require('shell’).openExternal(calc);

</script>

And then to get a Mattermost desktop user to click it, as displayed in this video.

And now for something completely different…

The really interesting thing about this (in my opinion) is not really the bug itself, but something else that I discovered: Mattermost is not alone in doing this. In fact, there is a whole bunch of desktop apps built on the same Node.js framework (Electron) out there, including some pretty high profile ones. While Electron supplies a fairly good security checklist, I think it’s safe to say that there is a number of easy mistakes to make when building an app like this, and that a number of the existing apps will have similar issues as the ones described in this post. I will be doing some research on potential attack vectors for client-side Node.js, including some of the existing apps, in the near future, and will possibly post about my findings at a later point.

For now, if you’re thinking about building a Node.js desktop app, please do the following:

Read and understand the security checklist supplied by Electron

Think long and hard about if you really need a desktop app, instead of just using a browser

The following statement from Electron’s security page basically sums it up:

“Again, this list merely minimizes the risk, it does not remove it. If your goal is to display a website, a browser will be a more secure option.”



Information about Mattermost security updates and how to apply patches can be found on the Mattermost Security Updates page.