Skip to content
This repository has been archived by the owner on Dec 11, 2019. It is now read-only.

Commit

Permalink
Merge pull request #15204 from brave/fix/15134
Browse files Browse the repository at this point in the history
Block privileged URL loads in context menu only
  • Loading branch information
bsclifton committed Sep 18, 2018
1 parent c5dd2a9 commit a0cd488
Show file tree
Hide file tree
Showing 8 changed files with 62 additions and 53 deletions.
2 changes: 1 addition & 1 deletion app/browser/menu.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ const createFileSubmenu = () => {
appActions.createTabRequested({
url: fileUrl(path),
windowId: focusedWindow.id
}, false, false, false, true)
})
})
}
})
Expand Down
6 changes: 0 additions & 6 deletions app/browser/reducers/tabsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ const {getFlashResourceId} = require('../../../js/flash')
const {l10nErrorText} = require('../../common/lib/httpUtil')
const flash = require('../../../js/flash')
const {isSourceAboutUrl, isTargetAboutUrl, isNavigatableAboutPage} = require('../../../js/lib/appUrlUtil')
const {isFileScheme, openableByContextMenu} = require('../../../js/lib/urlutil')
const {shouldDebugTabEvents} = require('../../cmdLine')

const getWebRTCPolicy = (state, tabId) => {
Expand Down Expand Up @@ -250,11 +249,6 @@ const tabsReducer = (state, action, immutableAction) => {
windows.focus(windowId)
}
const url = action.getIn(['createProperties', 'url'])
if (url && (!openableByContextMenu(url) ||
(isFileScheme(url) && !action.get('allowFile')))) {
// Don't allow 'open in new tab' to open file:// URLs for security
action = action.setIn(['createProperties', 'url'], 'about:blank')
}
setImmediate(() => {
if (action.get('activateIfOpen') ||
((isSourceAboutUrl(url) || isTargetAboutUrl(url)) && isNavigatableAboutPage(url))) {
Expand Down
6 changes: 0 additions & 6 deletions app/browser/reducers/windowsReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const {makeImmutable, isImmutable} = require('../../common/state/immutableUtil')
const electron = require('electron')
const BrowserWindow = electron.BrowserWindow
const firstDefinedValue = require('../../../js/lib/functional').firstDefinedValue
const {isFileScheme, openableByContextMenu} = require('../../../js/lib/urlutil')
const settings = require('../../../js/constants/settings')
const getSetting = require('../../../js/settings').getSetting

Expand Down Expand Up @@ -267,11 +266,6 @@ const handleCreateWindowAction = (state, action = Immutable.Map()) => {
if (Array.isArray(frameOpts)) {
frames = frameOpts
} else {
// Don't allow 'open in new window' to open a file:// URL for
// security reasons
if (isFileScheme(frameOpts.location) || !openableByContextMenu(frameOpts.location)) {
frameOpts.location = 'about:blank'
}
frames = [ frameOpts ]
}
} else {
Expand Down
23 changes: 14 additions & 9 deletions app/renderer/reducers/contextMenuReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const {makeImmutable, isMap} = require('../../common/state/immutableUtil')
const {getCurrentWindow, getCurrentWindowId} = require('../../renderer/currentWindow')
const {getSetting} = require('../../../js/settings')

const sanitizeUrl = urlUtil.sanitizeForContextMenu

const validateAction = function (action) {
action = makeImmutable(action)
assert.ok(isMap(action), 'action must be an Immutable.Map')
Expand Down Expand Up @@ -117,7 +119,7 @@ const openInNewTabMenuItem = (url, partitionNumber, openerTabId) => {
click: () => {
for (let i = 0; i < url.length; ++i) {
appActions.createTabRequested({
url: url[i],
url: sanitizeUrl(url[i]),
partitionNumber: partitionNumber[i],
openerTabId,
active
Expand All @@ -130,7 +132,7 @@ const openInNewTabMenuItem = (url, partitionNumber, openerTabId) => {
label: locale.translation('openInNewTab'),
click: () => {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
partitionNumber,
openerTabId,
active
Expand All @@ -148,7 +150,7 @@ const openInNewPrivateTabMenuItem = (url, openerTabId, isTor) => {
click: () => {
for (let i = 0; i < url.length; ++i) {
appActions.createTabRequested({
url: url[i],
url: sanitizeUrl(url[i]),
isPrivate: true,
isTor,
openerTabId,
Expand All @@ -162,7 +164,7 @@ const openInNewPrivateTabMenuItem = (url, openerTabId, isTor) => {
label: locale.translation(isTor ? 'openInNewTorTab' : 'openInNewPrivateTab'),
click: () => {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
isPrivate: true,
isTor,
openerTabId,
Expand All @@ -181,7 +183,7 @@ const openInNewSessionTabMenuItem = (url, openerTabId) => {
click: () => {
for (let i = 0; i < url.length; ++i) {
appActions.createTabRequested({
url: url[i],
url: sanitizeUrl(url[i]),
isPartitioned: true,
openerTabId,
active
Expand All @@ -194,7 +196,7 @@ const openInNewSessionTabMenuItem = (url, openerTabId) => {
label: locale.translation('openInNewSessionTab'),
click: () => {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
isPartitioned: true,
openerTabId,
active
Expand All @@ -208,7 +210,10 @@ const openInNewWindowMenuItem = (location, partitionNumber) => {
return {
label: locale.translation('openInNewWindow'),
click: () => {
appActions.newWindow({ location, partitionNumber })
appActions.newWindow({
location: sanitizeUrl(location),
partitionNumber
})
}
}
}
Expand Down Expand Up @@ -557,7 +562,7 @@ const onLongBackHistory = (state, action) => {
click: function (e) {
if (eventUtil.isForSecondaryAction(e)) {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
partitionNumber: action.get('partitionNumber'),
active: !!e.shiftKey
})
Expand Down Expand Up @@ -610,7 +615,7 @@ const onLongForwardHistory = (state, action) => {
click: function (e) {
if (eventUtil.isForSecondaryAction(e)) {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
partitionNumber: action.get('partitionNumber'),
active: !!e.shiftKey
})
Expand Down
4 changes: 1 addition & 3 deletions js/actions/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,15 +269,13 @@ const appActions = {
* switch to it instead of creating a new one
* @param {Boolean} isRestore when true, won't try to activate the new tab, even if the user preference indicates to
* @param {Boolean} focusWindow
* @param {Boolean} allowFile - When true, allows file:// URLs to be opened
*/
createTabRequested: function (createProperties, activateIfOpen = false, isRestore = false, focusWindow = false, allowFile = false) {
createTabRequested: function (createProperties, activateIfOpen = false, isRestore = false, focusWindow = false) {
dispatch({
actionType: appConstants.APP_CREATE_TAB_REQUESTED,
createProperties,
activateIfOpen,
isRestore,
allowFile,
focusWindow
})
},
Expand Down
27 changes: 17 additions & 10 deletions js/contextMenus.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const ledgerUtil = require('../app/common/lib/ledgerUtil')
const isDarwin = platformUtil.isDarwin()
const isLinux = platformUtil.isLinux()

const sanitizeUrl = urlUtil.sanitizeForContextMenu

/**
* Gets the correct search URL for the current frame.
* @param {Immutable.Map} activeFrame - currently active frame
Expand Down Expand Up @@ -764,7 +766,7 @@ const openInNewTabMenuItem = (url, partitionNumber, openerTabId) => {
click: () => {
for (let i = 0; i < url.length; ++i) {
appActions.createTabRequested({
url: url[i],
url: sanitizeUrl(url[i]),
partitionNumber: partitionNumber[i],
openerTabId,
active
Expand All @@ -777,7 +779,7 @@ const openInNewTabMenuItem = (url, partitionNumber, openerTabId) => {
label: locale.translation('openInNewTab'),
click: () => {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
partitionNumber,
openerTabId,
active
Expand All @@ -804,7 +806,7 @@ const openInNewPrivateTabMenuItem = (url, openerTabId, isTor) => {
click: () => {
for (let i = 0; i < url.length; ++i) {
appActions.createTabRequested({
url: url[i],
url: sanitizeUrl(url[i]),
isPrivate: true,
isTor,
openerTabId,
Expand All @@ -818,7 +820,7 @@ const openInNewPrivateTabMenuItem = (url, openerTabId, isTor) => {
label: locale.translation(isTor ? 'openInNewTorTab' : 'openInNewPrivateTab'),
click: () => {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
isPrivate: true,
isTor,
openerTabId,
Expand All @@ -833,7 +835,12 @@ const openInNewWindowMenuItem = (location, isPrivate, partitionNumber, isTor) =>
return {
label: locale.translation('openInNewWindow'),
click: () => {
appActions.newWindow({ location, isPrivate, isTor, partitionNumber })
appActions.newWindow({
location: sanitizeUrl(location),
isPrivate,
isTor,
partitionNumber
})
}
}
}
Expand All @@ -846,7 +853,7 @@ const openInNewSessionTabMenuItem = (url, openerTabId) => {
click: (item) => {
for (let i = 0; i < url.length; ++i) {
appActions.createTabRequested({
url: url[i],
url: sanitizeUrl(url[i]),
isPartitioned: true,
openerTabId,
active
Expand All @@ -859,7 +866,7 @@ const openInNewSessionTabMenuItem = (url, openerTabId) => {
label: locale.translation('openInNewSessionTab'),
click: (item) => {
appActions.createTabRequested({
url,
url: sanitizeUrl(url),
isPartitioned: true,
openerTabId,
active
Expand Down Expand Up @@ -912,7 +919,7 @@ const searchSelectionMenuItem = (location) => {
const isPrivate = frame.get('isPrivate')
const isTor = frameStateUtil.isTor(frame)
appActions.createTabRequested({
url: searchUrl,
url: sanitizeUrl(searchUrl),
isPrivate,
isTor,
partitionNumber: frame.get('partitionNumber'),
Expand Down Expand Up @@ -999,7 +1006,7 @@ function mainTemplateInit (nodeProps, frame, tab) {
click: (item) => {
if (nodeProps.srcURL) {
appActions.createTabRequested({
url: nodeProps.srcURL,
url: sanitizeUrl(nodeProps.srcURL),
openerTabId: frame.get('tabId'),
isPrivate,
isTor,
Expand Down Expand Up @@ -1027,7 +1034,7 @@ function mainTemplateInit (nodeProps, frame, tab) {
label: locale.translation('searchImage'),
click: () => {
appActions.createTabRequested({
url: searchUrl.replace('?q', 'byimage?image_url'),
url: sanitizeUrl(searchUrl.replace('?q', 'byimage?image_url')),
isPrivate,
isTor,
partitionNumber: frame.get('partitionNumber')
Expand Down
15 changes: 9 additions & 6 deletions js/lib/urlutil.js
Original file line number Diff line number Diff line change
Expand Up @@ -424,17 +424,20 @@ const UrlUtil = {

/**
* Checks if URL is safe to open via 'open in new tab/window' context menu
* If so, returns the URL. If not, returns about:blank.
* @param {string} url - URL to check
* @return {boolean}
* @return {string}
*/
openableByContextMenu: function (url) {
sanitizeForContextMenu: function (url) {
if (!url) {
return true
return url
}
const protocol = urlParse(url).protocol
// file: is untrusted but handled in a separate check
return ['http:', 'https:', 'ws:', 'wss:', 'magnet:', 'file:', 'data:',
'blob:', 'about:', 'chrome-extension:', 'view-source:'].includes(protocol)
if (['http:', 'https:', 'ws:', 'wss:', 'magnet:', 'data:', 'blob:',
'about:', 'chrome-extension:', 'view-source:'].includes(protocol)) {
return url
}
return 'about:blank'
},

/**
Expand Down
32 changes: 20 additions & 12 deletions test/unit/lib/urlutilTestComponents.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// lazy load requires for dual use in and outside muon
const urlUtil = () => require('../../../js/lib/urlutil')

const blank = 'about:blank'

module.exports = {
'getScheme': {
'null for empty': (test) => {
Expand Down Expand Up @@ -328,30 +330,36 @@ module.exports = {
}
},

'openableByContextMenu': {
'returns true when input:': {
'is an absolute file path with scheme': (test) => {
test.equal(urlUtil().openableByContextMenu('file:///file/path/to/file'), true)
},
'sanitizeForContextMenu': {
'returns original URL when input:': {
'is http': (test) => {
test.equal(urlUtil().openableByContextMenu('http://example.com'), true)
const url = 'http://example.com'
test.equal(urlUtil().sanitizeForContextMenu(url), url)
},
'is https': (test) => {
test.equal(urlUtil().openableByContextMenu('HTtpS://brave.com/?abc=1#test'), true)
const url = 'HTtpS://brave.com/?abc=1#test'
test.equal(urlUtil().sanitizeForContextMenu(url), url)
},
'is about:blank': (test) => {
test.equal(urlUtil().openableByContextMenu('about:blank'), true)
const url = 'about:blank'
test.equal(urlUtil().sanitizeForContextMenu(url), url)
},
'is empty': (test) => {
test.equal(urlUtil().openableByContextMenu(), true)
test.equal(urlUtil().sanitizeForContextMenu(), undefined)
}
},
'returns false when input:': {
'returns blank when input:': {
'is an absolute file path with scheme': (test) => {
test.equal(urlUtil().sanitizeForContextMenu('file:///file/path/to/file'), blank)
},
'is file without scheme': (test) => {
test.equal(urlUtil().sanitizeForContextMenu('/file/path/to/file'), blank)
},
'is chrome:': (test) => {
test.equal(urlUtil().openableByContextMenu('chrome://brave/etc/passwd'), false)
test.equal(urlUtil().sanitizeForContextMenu('chrome://brave/etc/passwd'), blank)
},
'is ssh:': (test) => {
test.equal(urlUtil().openableByContextMenu('ssh://test@127.0.0.1'), false)
test.equal(urlUtil().sanitizeForContextMenu('ssh://test@127.0.0.1'), blank)
}
}
},
Expand Down

0 comments on commit a0cd488

Please sign in to comment.