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

Commit

Permalink
Strip cookies from requests fetch favicons
Browse files Browse the repository at this point in the history
Fixes #14250
Auditors: @diracdeltas

Test Plan:
Manually navigate to  https://www.grc.com/cookies/forensics.htm and observe no problems in the report
Navigate to a website with a favicon (such as github.com) and observe the favicon rendering

Unit tests:
run the faviconUtil unit tests

Webdriver test:
Runs a test page that sets a 3rd party cookie and checks if the favicon request carries over the cookies
  • Loading branch information
Slava committed May 31, 2018
1 parent 21f24d0 commit cf38a06
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 9 deletions.
24 changes: 22 additions & 2 deletions app/filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const downloadStates = require('../js/constants/downloadStates')
const urlParse = require('./common/urlParse')
const getSetting = require('../js/settings').getSetting
const appUrlUtil = require('../js/lib/appUrlUtil')
const faviconUtil = require('../js/lib/faviconUtil')
const siteSettings = require('../js/state/siteSettings')
const settings = require('../js/constants/settings')
const userPrefs = require('../js/state/userPrefs')
Expand Down Expand Up @@ -57,6 +58,11 @@ const registeredSessions = {}
*/
const permissionCallbacks = {}

/**
* A set to keep track of URLs fetching favicons that need special treatment later
*/
const faviconURLs = new Set()

const getBraverySettingsForUrl = (url, appState, isPrivate) => {
const cachedBraverySettings = getBraverySettingsCache(url, isPrivate)
if (cachedBraverySettings) {
Expand Down Expand Up @@ -109,13 +115,21 @@ function registerForBeforeRequest (session, partition) {
}
}

const url = details.url
// filter out special urls for fetching favicons
if (faviconUtil.isWrappedFaviconUrl(url)) {
const redirectURL = faviconUtil.unwrapFaviconUrl(url)
faviconURLs.add(redirectURL)
muonCb({ redirectURL })
return
}

if (shouldIgnoreUrl(details)) {
muonCb({})
return
}

const firstPartyUrl = module.exports.getMainFrameUrl(details)
const url = details.url
// this can happen if the tab is closed and the webContents is no longer available
if (!firstPartyUrl) {
muonCb({ cancel: true })
Expand Down Expand Up @@ -301,9 +315,15 @@ function registerForBeforeSendHeaders (session, partition) {
const isPrivate = module.exports.isPrivate(partition)

session.webRequest.onBeforeSendHeaders(function (details, muonCb) {
// strip cookies from all requests fetching favicons
if (faviconURLs.has(details.url)) {
faviconURLs.delete(details.url)
delete details.requestHeaders['Cookie']
}

// Using an electron binary which isn't from Brave
if (shouldIgnoreUrl(details)) {
muonCb({})
muonCb({ requestHeaders: details.requestHeaders })
return
}

Expand Down
17 changes: 10 additions & 7 deletions app/renderer/rendererTabEvents.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const siteSettingsState = require('../common/state/siteSettingsState')
const contextMenuState = require('../common/state/contextMenuState')

const domUtil = require('./lib/domUtil')
const faviconUtil = require('../../js/lib/faviconUtil')
const imageUtil = require('../../js/lib/imageUtil')
const historyUtil = require('../common/lib/historyUtil')
const UrlUtil = require('../../js/lib/urlutil')
Expand Down Expand Up @@ -111,13 +112,15 @@ const api = module.exports = {
}
case 'page-favicon-updated': {
if (e.favicons &&
e.favicons.length > 0 &&
// Favicon changes lead to recalculation of top site data so only fire
// this when needed. Some sites update favicons very frequently.
e.favicons[0] !== frame.get('icon')) {
imageUtil.getWorkingImageUrl(e.favicons[0], (error) => {
windowActions.setFavicon(frame, error ? null : e.favicons[0])
})
e.favicons.length > 0) {
const url = faviconUtil.wrapFaviconUrl(e.favicons[0])
// Favicon changes lead to recalculation of top site data so only fire
// this when needed. Some sites update favicons very frequently.
if (url !== frame.get('icon')) {
imageUtil.getWorkingImageUrl(url, (error) => {
windowActions.setFavicon(frame, error ? null : url)
})
}
}
break
}
Expand Down
11 changes: 11 additions & 0 deletions js/lib/faviconUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */

const suffix = '#brave-favicon-fragment'

module.exports = {
wrapFaviconUrl: (url) => url && `${url}${suffix}`,
unwrapFaviconUrl: (url) => url && url.substring(0, url.length - suffix.length),
isWrappedFaviconUrl: (url) => !!(url && url.endsWith(suffix))
}
24 changes: 24 additions & 0 deletions test/bravery-components/braveryPanelTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -1053,4 +1053,28 @@ describe('Bravery Panel', function () {
})
})
})

// see #14250
describe('Favicon requests cannot set cookies', function () {
Brave.beforeEach(this)
beforeEach(function * () {
yield setup(this.app.client)
})
it('does not allow favicon request to set cookie', function * () {
const url = Brave.server.url('cookie-favicon.html').replace('localhost', '127.0.0.1')
const testResultUrl = Brave.server.url('cookie-favicon-test-result.html')
yield this.app.client
.tabByIndex(0)
.loadUrl(url)
.openBraveMenu(braveMenu, braveryPanel)
.click(cookieControl)
.waitForVisible(allowAllCookiesOption)
.click(allowAllCookiesOption)
.tabByIndex(0)
.loadUrl(url)
.waitForExist('head>link')
.loadUrl(testResultUrl)
.waitForTextValue('body', 'pass')
})
})
})
17 changes: 17 additions & 0 deletions test/fixtures/cookie-favicon-test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<html>
<head>
</head>
<body>
cookies:
<div id='cookie-favicon'>
</div>

<script>
function setText (id, result) {
document.getElementById(id).innerText = JSON.stringify(result)
}

setText('cookie-favicon', document.cookie)
</script>
</body>
</html>
37 changes: 37 additions & 0 deletions test/fixtures/cookie-favicon.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<html>
<head>
</head>
<body>

<iframe id='test-frame' height=200 width=500></iframe>

<script>
var host = window.location + ''
var parts = host.split('/')
parts.splice(-1, 1)
host = parts.join('/')

function loadIco() {
var faviconLocation = host.replace('127.0.0.1', 'localhost') + '/cookie-favicon.ico'
var link = document.createElement('link')
link.rel = 'shortcut icon'
link.href = faviconLocation
document.head.appendChild(link);
}


function loadFrame() {
var testPageLocation = host.replace('127.0.0.1', 'localhost') + '/cookie-favicon-test.html'
var frame = document.getElementById('test-frame')
frame.onload = loadIco
frame.src = testPageLocation
}

loadFrame()

function setText (id, result) {
document.getElementById(id).innerText = JSON.stringify(result)
}
</script>
</body>
</html>
28 changes: 28 additions & 0 deletions test/lib/serverChild.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ Server.prototype = {
start: function (port) {
// using node-static for now we can do fancy stuff in the future.
var file = new nodeStatic.Server(root)

// temporary state to control 3rd-party fingerprinting with favicons
var faviconCookieDetected = false
this.http = http.createServer(function (req, res) {
req.addListener('end', function () {
// Handle corked urls.
Expand Down Expand Up @@ -82,6 +85,31 @@ Server.prototype = {
return
}

// the following routes are setup to test 3rd-party cookie sharing over favicons
if (req.url === '/cookie-favicon.ico') {
if (req.headers.cookie && req.headers.cookie !== '') {
faviconCookieDetected = true
}
file.servePath('/img/test.ico', 200, {}, req, res, () => res.end())
return
}

if (req.url === '/cookie-favicon-test.html') {
file.servePath(req.url, 200, {
'Set-Cookie': 'new-cookie'
}, req, res, () => res.end())
return
}

if (req.url === '/cookie-favicon-test-result.html') {
res.writeHead(200, {
'Content-Type': 'text/html'
})
const text = faviconCookieDetected ? 'fail' : 'pass';
res.end(`<body>${text}</body>`);
return
}

// hand off request to node-static
file.serve(req, res)
}).resume()
Expand Down
41 changes: 41 additions & 0 deletions test/unit/js/lib/faviconUtilTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
/* global describe, it */

const faviconUtil = require('../../../../js/lib/faviconUtil')
const assert = require('assert')

require('../../braveUnit')

describe('faviconUtil', function () {
it('wraps, identifies and unwarps URLs', function () {
const url = 'http://webserver.com/favicon.ico'
const wrappedUrl = faviconUtil.wrapFaviconUrl(url)
const unwrappedUrl = faviconUtil.unwrapFaviconUrl(wrappedUrl)

assert.equal(faviconUtil.isWrappedFaviconUrl(url), false)
assert.equal(faviconUtil.isWrappedFaviconUrl(wrappedUrl), true)
assert.equal(faviconUtil.isWrappedFaviconUrl(unwrappedUrl), false)
assert.equal(url, unwrappedUrl)
})
it('wraps, identifies and unwarps URLs with query params', function () {
const url = 'http://webserver.com/favicon.ico?qp=1&qp2=2'
const wrappedUrl = faviconUtil.wrapFaviconUrl(url)
const unwrappedUrl = faviconUtil.unwrapFaviconUrl(wrappedUrl)

assert.equal(faviconUtil.isWrappedFaviconUrl(url), false)
assert.equal(faviconUtil.isWrappedFaviconUrl(wrappedUrl), true)
assert.equal(faviconUtil.isWrappedFaviconUrl(unwrappedUrl), false)
assert.equal(url, unwrappedUrl)
})
it('works with undefined', function () {
const url = undefined
const wrappedUrl = faviconUtil.wrapFaviconUrl(url)
const unwrappedUrl = faviconUtil.unwrapFaviconUrl(wrappedUrl)

assert.equal(faviconUtil.isWrappedFaviconUrl(url), false)
assert.equal(wrappedUrl, undefined)
assert.equal(unwrappedUrl, undefined)
})
})

0 comments on commit cf38a06

Please sign in to comment.