From 63ef9554dde9711106afb197130bcf7397d0360b Mon Sep 17 00:00:00 2001 From: TheGoddessInari Date: Fri, 24 Apr 2020 17:47:59 -0700 Subject: [PATCH] Escape title user inputs. Fix our version of CVE-2019-17625. We take the simple/cheap way out and use ExtJS htmlEncode on all entry points for this. This is still mostly limited to 'doing it to yourself'. The main fix is in app/view/main/Main.js where the title is rendered out, and will apparently execute arbitrary javascript within a title tag(!). This is an ExtJS thing, apparently, so we make it unconditionally encode it to render on the bar. Apparently this isn't the only place arbitrary execution can occur, so just be safe(r). --- app/Application.js | 8 ++++---- app/store/Services.js | 2 +- app/util/IconLoader.js | 4 ++-- app/util/Notifier.js | 4 ++-- app/ux/WebView.js | 4 ++-- app/view/add/Add.js | 4 ++-- app/view/add/AddController.js | 2 +- app/view/main/Main.js | 3 ++- app/view/main/MainController.js | 12 ++++++------ app/view/preferences/Preferences.js | 2 +- 10 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/Application.js b/app/Application.js index 9abc09c4..5f5b0b04 100644 --- a/app/Application.js +++ b/app/Application.js @@ -65,15 +65,15 @@ Ext.define('Hamsket.Application', { newValue = parseInt(newValue); if ( newValue > 0 ) { if ( Ext.cq1('app-main').getActiveTab().record ) { - document.title = 'Hamsket (' + Hamsket.util.Format.formatNumber(newValue) + ') - '+Ext.cq1('app-main').getActiveTab().record.get('name'); + document.title = `Hamsket (${Hamsket.util.Format.formatNumber(newValue)}) - ${Ext.String.htmlEncode(Ext.cq1('app-main').getActiveTab().record.get('name'))}`; } else { - document.title = 'Hamsket (' + Hamsket.util.Format.formatNumber(newValue) + ')'; + document.title = `Hamsket (${Hamsket.util.Format.formatNumber(newValue)})`; } } else { if ( Ext.cq1('app-main') && Ext.cq1('app-main').getActiveTab().record ) { - document.title = 'Hamsket - '+Ext.cq1('app-main').getActiveTab().record.get('name'); + document.title = `Hamsket - ${Ext.String.htmlEncode(Ext.cq1('app-main').getActiveTab().record.get('name'))}`; } else { - document.title = 'Hamsket'; + document.title = `Hamsket`; } } } diff --git a/app/store/Services.js b/app/store/Services.js index 9ce81063..3b85db55 100644 --- a/app/store/Services.js +++ b/app/store/Services.js @@ -32,7 +32,7 @@ Ext.define('Hamsket.store.Services', { const cfg = { xtype: 'webview' ,id: 'tab_'+service.get('id') - ,title: service.get('name') + ,title: Ext.String.htmlEncode(service.get('name')) ,icon: service.get('type') !== 'custom' ? 'resources/icons/'+service.get('logo') : ( service.get('logo') === '' ? 'resources/icons/custom.png' : service.get('logo')) ,src: service.get('url') ,type: service.get('type') diff --git a/app/util/IconLoader.js b/app/util/IconLoader.js index 8aabca44..42b87a95 100644 --- a/app/util/IconLoader.js +++ b/app/util/IconLoader.js @@ -39,8 +39,8 @@ Ext.define('Hamsket.util.IconLoader', { return bg.slice(5, -2); })();`).then(function (backgroundImage) { if (backgroundImage) { - service.setTitle(``+service.title); - service.fireEvent('iconchange', service, backgroundImage, service.icon); + service.setTitle(`${Ext.String.htmlEncode(service.title)}`); + service.fireEvent('iconchange', service, Ext.String.htmlEncode(backgroundImage), service.icon); } return true; } diff --git a/app/util/Notifier.js b/app/util/Notifier.js index 206d0665..65c67ab5 100644 --- a/app/util/Notifier.js +++ b/app/util/Notifier.js @@ -42,8 +42,8 @@ Ext.define('Hamsket.util.Notifier', { this.dispatchNotification = function(view, count) { const text = getNotificationText(view, count); - const notification = new Notification(view.record.get('name'), { - body: text, + const notification = new Notification(Ext.String.htmlEncode(view.record.get('name')), { + body: Ext.String.htmlEncode(text), icon: view.icon, silent: view.record.get('muted') }); diff --git a/app/ux/WebView.js b/app/ux/WebView.js index 5be882d7..b4a7d3f9 100644 --- a/app/ux/WebView.js +++ b/app/ux/WebView.js @@ -39,7 +39,7 @@ Ext.define('Hamsket.ux.WebView',{ Ext.apply(me, { items: me.webViewConstructor() - ,title: me.record.get('tabname') ? me.record.get('name') : '' + ,title: me.record.get('tabname') ? Ext.String.htmlEncode(me.record.get('name')) : '' ,icon: me.record.get('type') === 'custom' ? (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo')) : 'resources/icons/'+me.record.get('logo') ,src: me.record.get('url') ,type: me.record.get('type') @@ -297,7 +297,7 @@ Ext.define('Hamsket.ux.WebView',{ if ( e.url.indexOf('slack.com/call/') >= 0 ) { me.add({ xtype: 'window' - ,title: e.options.title + ,title: Ext.String.htmlEncode(e.options.title) ,width: e.options.width ,height: e.options.height ,maximizable: true diff --git a/app/view/add/Add.js b/app/view/add/Add.js index 628b5a92..c80dc8ae 100644 --- a/app/view/add/Add.js +++ b/app/view/add/Add.js @@ -28,7 +28,7 @@ Ext.define('Hamsket.view.add.Add',{ ,initComponent() { const me = this; - me.title = (!me.edit ? locale['app.window[0]'] : locale['app.window[1]']) + ' ' + me.record.get('name'); + me.title = `${(!me.edit ? locale['app.window[0]'] : locale['app.window[1]'])} ${Ext.String.htmlEncode(me.record.get('name'))}`; me.icon = me.record.get('type') === 'custom' ? (!me.edit ? 'resources/icons/custom.png' : (me.record.get('logo') === '' ? 'resources/icons/custom.png' : me.record.get('logo'))) : 'resources/icons/'+me.record.get('logo'); me.items = [ { @@ -38,7 +38,7 @@ Ext.define('Hamsket.view.add.Add',{ xtype: 'textfield' ,fieldLabel: locale['app.window[2]'] ,labelWidth: 40 - ,value: me.record.get('type') === 'custom' ? (me.edit ? me.record.get('name') : '') : me.record.get('name') + ,value: me.record.get('type') === 'custom' ? (me.edit ? Ext.String.htmlEncode(me.record.get('name')) : '') : Ext.String.htmlEncode(me.record.get('name')) ,name: 'serviceName' ,allowBlank: true ,listeners: { specialkey: 'onEnter' } diff --git a/app/view/add/AddController.js b/app/view/add/AddController.js index 974c81a8..dda88a37 100644 --- a/app/view/add/AddController.js +++ b/app/view/add/AddController.js @@ -52,7 +52,7 @@ Ext.define('Hamsket.view.add.AddController', { const view = Ext.getCmp('tab_'+win.record.get('id')); // Change the title of the Tab - view.setTitle( formValues.tabname ? formValues.serviceName : '' ); + view.setTitle( formValues.tabname ? Ext.String.htmlEncode(formValues.serviceName) : '' ); // Change sound of the Tab view.setAudioMuted(formValues.muted); // Change notifications of the Tab diff --git a/app/view/main/Main.js b/app/view/main/Main.js index da8aa88a..e4bfcfad 100644 --- a/app/view/main/Main.js +++ b/app/view/main/Main.js @@ -171,7 +171,8 @@ Ext.define('Hamsket.view.main.Main', { ,editor: { xtype: 'textfield' ,allowBlank: true - } + }, + renderer: Ext.String.htmlEncode } ,{ xtype: 'actioncolumn' diff --git a/app/view/main/MainController.js b/app/view/main/MainController.js index c617d4f0..040f9826 100644 --- a/app/view/main/MainController.js +++ b/app/view/main/MainController.js @@ -44,9 +44,9 @@ Ext.define('Hamsket.view.main.MainController', { // Update the main window so it includes the active tab title. if ( Hamsket.app.getTotalNotifications() > 0 ) { - document.title = 'Hamsket ('+ Hamsket.app.getTotalNotifications() +') - ' + newTab.record.get('name'); + document.title = `Hamsket (${Hamsket.app.getTotalNotifications()}) - ${Ext.String.htmlEncode(newTab.record.get('name'))}`; } else { - document.title = 'Hamsket - ' + newTab.record.get('name'); + document.title = `Hamsket - ${Ext.String.htmlEncode(newTab.record.get('name'))}`; } } @@ -90,7 +90,7 @@ Ext.define('Hamsket.view.main.MainController', { e.record.commit(); // Change the title of the Tab - Ext.getCmp('tab_'+e.record.get('id')).setTitle(e.record.get('name')); + Ext.getCmp('tab_'+e.record.get('id')).setTitle(Ext.String.htmlEncode(e.record.get('name'))); } ,onEnableDisableService(cc, rowIndex, checked, obj, hideTab) { @@ -102,7 +102,7 @@ Ext.define('Hamsket.view.main.MainController', { Ext.cq1('app-main').insert(rec.get('align') === 'left' ? rec.get('position') : rec.get('position')+1, { xtype: 'webview' ,id: 'tab_'+rec.get('id') - ,title: rec.get('name') + ,title: `${Ext.String.htmlEncode(rec.get('name'))}` ,icon: rec.get('type') !== 'custom' ? 'resources/icons/'+rec.get('logo') : ( rec.get('logo') === '' ? 'resources/icons/custom.png' : rec.get('logo')) ,src: rec.get('url') ,type: rec.get('type') @@ -183,7 +183,7 @@ Ext.define('Hamsket.view.main.MainController', { ,removeService( gridView, rowIndex, colIndex, col, e, rec, rowEl ) { const me = this; - Ext.Msg.confirm(locale['app.window[12]'], locale['app.window[13]']+' '+rec.get('name')+'?', function(btnId) { + Ext.Msg.confirm(locale['app.window[12]'], locale['app.window[13]']+' '+Ext.String.htmlEncode(rec.get('name'))+'?', function(btnId) { if ( btnId === 'yes' ) { Ext.Msg.wait('Please wait until we clear all.', 'Removing...'); me.removeServiceFn(rec.get('id'), 1, 1); @@ -281,7 +281,7 @@ Ext.define('Hamsket.view.main.MainController', { fn(record) { if ( record.get('type') === 'custom' ) return true; if ( !Ext.Array.contains(Ext.Object.getKeys(cg.getValue()), record.get('type')) ) return false; - return record.get('name').toLowerCase().indexOf(newValue.toLowerCase()) > -1 ? true : false; + return Ext.String.htmlEncode(record.get('name')).toLowerCase().indexOf(newValue.toLowerCase()) > -1 ? true : false; } }); } else { diff --git a/app/view/preferences/Preferences.js b/app/view/preferences/Preferences.js index f3567341..7ecc57bb 100644 --- a/app/view/preferences/Preferences.js +++ b/app/view/preferences/Preferences.js @@ -44,7 +44,7 @@ Ext.define('Hamsket.view.preferences.Preferences',{ Ext.getStore('Services').each(function(rec) { defaultServiceOptions.push({ value: rec.get('id') - ,label: rec.get('name') + ,label: Ext.String.htmlEncode(rec.get('name')) }); });