From fd7c4fabbd80ff7223f34e88b2db6a45c89733f7 Mon Sep 17 00:00:00 2001 From: kkapsner Date: Mon, 27 Aug 2018 00:30:48 +0200 Subject: [PATCH] Added protection for history.length As requested by #231. --- .../addon description/de/description.txt | 1 + .../addon description/en/description.txt | 1 + _locales/de/messages.json | 29 ++++++++++ _locales/en/messages.json | 29 ++++++++++ lib/askForPermission.js | 9 ++- lib/modifiedAPI.js | 1 + lib/modifiedHistoryAPI.js | 56 +++++++++++++++++++ lib/settingDefinitions.js | 9 ++- manifest.json | 1 + options/settingsDisplay.js | 7 +++ releaseNotes.txt | 1 + test/detectionTest.js | 36 +++++++----- 12 files changed, 161 insertions(+), 19 deletions(-) create mode 100644 lib/modifiedHistoryAPI.js diff --git a/.documentation/addon description/de/description.txt b/.documentation/addon description/de/description.txt index 948a3a4..61aa60d 100644 --- a/.documentation/addon description/de/description.txt +++ b/.documentation/addon description/de/description.txt @@ -18,6 +18,7 @@ Geschützte "Fingerprinting"-APIs:
  • canvas 2d
  • webGL
  • audio
  • +
  • history
  • Falls Sie Fehler finden oder Verbesserungsvorschläge haben, teilen Sie mir das bitte auf https://github.com/kkapsner/CanvasBlocker/issues mit. \ No newline at end of file diff --git a/.documentation/addon description/en/description.txt b/.documentation/addon description/en/description.txt index 891655c..2dcb180 100644 --- a/.documentation/addon description/en/description.txt +++ b/.documentation/addon description/en/description.txt @@ -18,6 +18,7 @@ Protected "fingerprinting" APIs:
  • canvas 2d
  • webGL
  • audio
  • +
  • history
  • Please report issues and feature requests at https://github.com/kkapsner/CanvasBlocker/issues diff --git a/_locales/de/messages.json b/_locales/de/messages.json index b027b8a..1e00553 100644 --- a/_locales/de/messages.json +++ b/_locales/de/messages.json @@ -93,6 +93,10 @@ "message": "Audio API", "description": "" }, + "section_history-api":{ + "message": "History API", + "description": "" + }, "displayAdvancedSettings_title": { "message": "Expertenmodus", @@ -177,6 +181,18 @@ "message": "Wollen Sie das Auslesen über die Audio-API erlauben?", "description": "" }, + "askForHistoryPermission": { + "message": "Wollen Sie die History-API erlauben?", + "description": "" + }, + "askForHistoryInputPermission": { + "message": "Wollen Sie das Schreiben in über die History-API erlauben?", + "description": "" + }, + "askForHistoryReadoutPermission": { + "message": "Wollen Sie das Auslesen über die History-API erlauben?", + "description": "" + }, "askOnlyOnce_title": { "message": "Nur einmal nachfragen", "description": "" @@ -500,6 +516,10 @@ "message": "Audioauslese vorgetäuscht auf {url}", "description": "" }, + "fakedHistoryReadout": { + "message": "History-Auslese vorgetäuscht auf {url}", + "description": "" + }, "fakedInput": { "message": "Bei Ausgabe vorgetäuscht auf {url}", "description": "" @@ -804,6 +824,15 @@ "description": "" }, + "historyLengthThreshold_title": { + "message": "History-Längenschwellwert", + "description": "" + }, + "historyLengthThreshold_description": { + "message": "Maximale Länge der Browser-History, die der Web-Seite mitgeteilt wird.", + "description": "" + }, + "theme_title": { "message": "Theme", "description": "" diff --git a/_locales/en/messages.json b/_locales/en/messages.json index b1e6411..9272887 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -93,6 +93,10 @@ "message": "Audio API", "description": "" }, + "section_history-api":{ + "message": "History API", + "description": "" + }, "displayAdvancedSettings_title": { "message": "Expert mode", @@ -177,6 +181,18 @@ "message": "Do you want to allow audio readout?", "description": "" }, + "askForHistoryPermission": { + "message": "Do you want to allow the history API?", + "description": "" + }, + "askForHistoryInputPermission": { + "message": "Do you want to allow history-API input?", + "description": "" + }, + "askForHistoryReadoutPermission": { + "message": "Do you want to allow history readout?", + "description": "" + }, "askOnlyOnce_title": { "message": "Ask only once", "description": "" @@ -500,6 +516,10 @@ "message": "Faked audio readout on {url}", "description": "" }, + "fakedHistoryReadout": { + "message": "Faked history readout on {url}", + "description": "" + }, "fakedInput": { "message": "Faked at input on {url}", "description": "" @@ -803,6 +823,15 @@ "description": "" }, + "historyLengthThreshold_title": { + "message": "History length threshold", + "description": "" + }, + "historyLengthThreshold_description": { + "message": "Maximal length of the history that is reported to the web site.", + "description": "" + }, + "theme_title": { "message": "Theme", "description": "" diff --git a/lib/askForPermission.js b/lib/askForPermission.js index 1769134..bc2c7b0 100644 --- a/lib/askForPermission.js +++ b/lib/askForPermission.js @@ -86,7 +86,8 @@ visible: _("askForVisiblePermission"), invisible: _("askForInvisiblePermission"), nocanvas: _("askForPermission"), - audio: _("askForAudioPermission") + audio: _("askForAudioPermission"), + history: _("askForHistoryPermission"), }, askStatus: { alreadyAsked: {}, @@ -98,7 +99,8 @@ visible: _("askForVisibleInputPermission"), invisible: _("askForInvisibleInputPermission"), nocanvas: _("askForInputPermission"), - audio: _("askForAudioInputPermission") + audio: _("askForAudioInputPermission"), + history: _("askForHistoryInputPermission"), }, askStatus: { alreadyAsked: {}, @@ -110,7 +112,8 @@ visible: _("askForVisibleReadoutPermission"), invisible: _("askForInvisibleReadoutPermission"), nocanvas: _("askForReadoutPermission"), - audio: _("askForAudioReadoutPermission") + audio: _("askForAudioReadoutPermission"), + history: _("askForHistoryReadoutPermission"), }, askStatus: { alreadyAsked: {}, diff --git a/lib/modifiedAPI.js b/lib/modifiedAPI.js index 5db6834..7149c1d 100644 --- a/lib/modifiedAPI.js +++ b/lib/modifiedAPI.js @@ -486,4 +486,5 @@ }); } appendModified(modifiedAudioAPI); + appendModified(require("./modifiedHistoryAPI")); }()); \ No newline at end of file diff --git a/lib/modifiedHistoryAPI.js b/lib/modifiedHistoryAPI.js new file mode 100644 index 0000000..b9b8fee --- /dev/null +++ b/lib/modifiedHistoryAPI.js @@ -0,0 +1,56 @@ +/* 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/. */ +(function(){ + "use strict"; + + var scope; + if ((typeof exports) !== "undefined"){ + scope = exports; + } + else { + window.scope.modifiedHistoryAPI = {}; + scope = window.scope.modifiedHistoryAPI; + } + + const {hasType, checkerWrapper} = require("./modifiedAPIFunctions"); + + scope.changedGetters = [ + { + objectGetters: [function(window){return window.History.prototype;}], + name: "length", + getterGenerator: function(checker){ + const temp = { + get length(){ + return checkerWrapper(checker, this, arguments, function(args, check){ + const {prefs, notify, window, original} = check; + const originalLength = original.apply(this, window.Array.from(args)); + const threshold = prefs("historyLengthThreshold", window.location); + if (originalLength > threshold){ + notify("fakedHistoryReadout"); + return threshold; + } + else { + return originalLength; + } + }); + } + }; + return Object.getOwnPropertyDescriptor(temp, "length").get; + } + } + ]; + + + function getStatus(obj, status){ + status = Object.create(status); + status.active = hasType(status, "readout"); + return status; + } + + scope.changedGetters.forEach(function(changedGetter){ + changedGetter.type = "readout"; + changedGetter.getStatus = getStatus; + changedGetter.api = "history"; + }); +}()); \ No newline at end of file diff --git a/lib/settingDefinitions.js b/lib/settingDefinitions.js index b59ca2b..212fa1e 100644 --- a/lib/settingDefinitions.js +++ b/lib/settingDefinitions.js @@ -89,7 +89,9 @@ {name: "Audio-API", level: 1}, "getFloatFrequencyData", "getByteFrequencyData", "getFloatTimeDomainData", "getByteTimeDomainData", "getChannelData", "copyFromChannel", - "getFrequencyResponse" + "getFrequencyResponse", + {name: "History-API", level: 1}, + "length", ], defaultKeyValue: false }, @@ -188,6 +190,7 @@ keys: [ "canvas", "audio", + "history", ], defaultKeyValue: false }, @@ -236,6 +239,10 @@ return Math.floor(Math.random() * 30).toString(10); } }, + { + name: "historyLengthThreshold", + defaultValue: 2 + }, { name: "blockDataURLs", defaultValue: true diff --git a/manifest.json b/manifest.json index 431ba68..1d8bfb1 100644 --- a/manifest.json +++ b/manifest.json @@ -37,6 +37,7 @@ "lib/hash.js", "lib/modifiedAPIFunctions.js", "lib/modifiedAudioAPI.js", + "lib/modifiedHistoryAPI.js", "lib/modifiedAPI.js", "lib/randomSupplies.js", "lib/intercept.js", diff --git a/options/settingsDisplay.js b/options/settingsDisplay.js index 9572f4b..4f03593 100644 --- a/options/settingsDisplay.js +++ b/options/settingsDisplay.js @@ -390,6 +390,13 @@ } ] }, + "History-API", + { + "name": "historyLengthThreshold", + "displayDependencies": { + "displayAdvancedSettings": [true] + } + }, "misc", { "name": "theme" diff --git a/releaseNotes.txt b/releaseNotes.txt index f4c693d..76a1cea 100644 --- a/releaseNotes.txt +++ b/releaseNotes.txt @@ -11,6 +11,7 @@ Version 0.5.3: - added theme for browser action popup - added badge - added option to ignore APIs + - added protection for history length fixes: - CSP did not work properly for worker-src diff --git a/test/detectionTest.js b/test/detectionTest.js index 8a784c7..6efc75a 100644 --- a/test/detectionTest.js +++ b/test/detectionTest.js @@ -96,22 +96,28 @@ addTest("function length", function(log){ addTest("function code", function(log){ "use strict"; var codeDetected = false; - if ( - !CanvasRenderingContext2D.prototype.getImageData.toString().match( - /^\s*function getImageData\s*\(\)\s*\{\s*\[native code\]\s*\}\s*$/ - ) - ){ - log("unexpected function code:", CanvasRenderingContext2D.prototype.getImageData.toString()); - codeDetected = true; - } - if ( - !HTMLCanvasElement.prototype.toDataURL.toString().match( - /^\s*function toDataURL\s*\(\)\s*\{\s*\[native code\]\s*\}\s*$/ - ) - ){ - log("unexpected function code:", HTMLCanvasElement.prototype.toDataURL.toString()); - codeDetected = true; + function checkFunctionCode(func, expectedName){ + log("checking", expectedName); + if (!func.toString().match( + new RegExp("^\\s*function " + expectedName + "\\s*\\(\\)\\s*\\{\\s*\\[native code\\]\\s*\\}\\s*$") + )){ + log("unexpected function code:", func.toString()); + return true; + } + return false; } + codeDetected = checkFunctionCode( + CanvasRenderingContext2D.prototype.getImageData, + "getImageData" + ) || codeDetected; + codeDetected = checkFunctionCode( + HTMLCanvasElement.prototype.toDataURL, + "toDataURL" + ) || codeDetected; + codeDetected = checkFunctionCode( + history.__lookupGetter__("length"), + "get length" + ) || codeDetected; return codeDetected; }); addTest("toString modified", function(log){