Improved iFrame protection

This commit is contained in:
kkapsner 2019-05-22 23:37:23 +02:00
parent 92ff2ff5d4
commit 2da680bfcb
4 changed files with 162 additions and 34 deletions

View File

@ -11,6 +11,7 @@
const {check: originalCheck, checkStack: originalCheckStack} = require("./check");
const {getWrapped} = require("./modifiedAPIFunctions");
const extension = require("./extension");
const iframeProtection = require("./iframeProtection");
const logging = require("./logging");
const {error, warning, message, notice, verbose, setPrefix: setLogPrefix} = logging;
@ -149,41 +150,16 @@
{check, checkStack, ask: askWrapper, notify, prefs}
);
message("prepare to intercept (i)frames.");
[window.HTMLIFrameElement, window.HTMLFrameElement].forEach(function(constructor){
var oldContentWindowGetter = constructor.prototype.__lookupGetter__("contentWindow");
Object.defineProperty(
getWrapped(constructor.prototype),
"contentWindow",
{
enumerable: true,
configurable: true,
get: exportFunction(function(){
var window = oldContentWindowGetter.call(this);
if (window){
interceptWindow(window);
}
return window;
}, window)
function interceptAllFrames(){
const currentLength = window.length;
for (let i = currentLength; i--;){
if (!interceptedWindows.get(wrappedWindow[i])){
interceptWindow(window[i]);
}
);
var oldContentDocumentGetter = constructor.prototype.__lookupGetter__("contentDocument");
Object.defineProperty(
getWrapped(constructor.prototype),
"contentDocument",
{
enumerable: true,
configurable: true,
get: exportFunction(function(){
var document = oldContentDocumentGetter.call(this);
if (document){
interceptWindow(document.defaultView);
}
return document;
}, window)
}
);
});
}
}
iframeProtection.protect(window, wrappedWindow, interceptWindow, interceptAllFrames);
const matchMediaDescriptor = Object.getOwnPropertyDescriptor(wrappedWindow, "matchMedia");
const originalMatchMedia = matchMediaDescriptor.value;

150
lib/iframeProtection.js Normal file
View File

@ -0,0 +1,150 @@
/* 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";
let scope;
if ((typeof exports) !== "undefined"){
scope = exports;
}
else {
scope = require.register("./iframeProtection", {});
}
const {getWrapped} = require("./modifiedAPIFunctions");
scope.protect = function protect(window, wrappedWindow, singleCallback, allCallback){
[window.HTMLIFrameElement, window.HTMLFrameElement].forEach(function(constructor){
var oldContentWindowGetter = constructor.prototype.__lookupGetter__("contentWindow");
Object.defineProperty(
getWrapped(constructor.prototype),
"contentWindow",
{
enumerable: true,
configurable: true,
get: exportFunction(function(){
var window = oldContentWindowGetter.call(this);
if (window){
singleCallback(window);
}
return window;
}, window)
}
);
var oldContentDocumentGetter = constructor.prototype.__lookupGetter__("contentDocument");
Object.defineProperty(
getWrapped(constructor.prototype),
"contentDocument",
{
enumerable: true,
configurable: true,
get: exportFunction(function(){
var document = oldContentDocumentGetter.call(this);
if (document){
singleCallback(document.defaultView);
}
return document;
}, window)
}
);
});
[
// useless as length could be obtained before the iframe is created and window.frames === window
// {
// object: wrappedWindow,
// methods: [],
// getters: ["length", "frames"],
// setters: []
// },
{
object: wrappedWindow.Node.prototype,
methods: ["appendChild", "insertBefore", "replaceChild"],
getters: [],
setters: []
},
{
object: wrappedWindow.Element.prototype,
methods: [
"append", "prepend",
"insertAdjacentElement", "insertAdjacentHTML", "insertAdjacentText",
"replaceWith"
],
getters: [],
setters: ["innerHTML", "outerHTML"]
}
].forEach(function(protectionDefinition){
const object = protectionDefinition.object;
protectionDefinition.methods.forEach(function(method){
const descriptor = Object.getOwnPropertyDescriptor(object, method);
const original = descriptor.value;
descriptor.value = exportFunction(eval(`(function ${method}(){
const value = arguments.length?
original.apply(this, window.Array.from(arguments)):
original.call(this);
allCallback();
return value;
})`), window);
Object.defineProperty(object, method, descriptor);
});
protectionDefinition.getters.forEach(function(property){
const descriptor = Object.getOwnPropertyDescriptor(object, property);
const temp = eval(`({
get ${property}(){
const ret = this.${property};
allCallback();
return ret;
}
})`);
descriptor.get = exportFunction(Object.getOwnPropertyDescriptor(temp, property).get, window);
Object.defineProperty(object, property, descriptor);
});
protectionDefinition.setters.forEach(function(property){
const descriptor = Object.getOwnPropertyDescriptor(object, property);
const temp = eval(`({
set ${property}(value){
const ret = this.${property} = value;
allCallback();
return ret;
}
})`);
descriptor.set = exportFunction(Object.getOwnPropertyDescriptor(temp, property).set, window);
Object.defineProperty(object, property, descriptor);
});
});
// MutationObserver to intercept iframes while generating the DOM.
var observer = new MutationObserver(allCallback);
observer.observe(window.document.documentElement, {subtree: true, childList: true});
window.document.addEventListener("DOMContentLoaded", function(){
observer.disconnect();
});
// MutationObserver does not trigger fast enough when document.write is used
const documentWriteDescriptor = Object.getOwnPropertyDescriptor(wrappedWindow.HTMLDocument.prototype, "write");
const documentWrite = documentWriteDescriptor.value;
documentWriteDescriptor.value = exportFunction(function write(str){
const parts = str.split(/(?=>)/);
const length = parts.length;
for (let i = 0; i < length; i += 1){
documentWrite.call(this, parts[i]);
allCallback();
}
}, window);
Object.defineProperty(wrappedWindow.HTMLDocument.prototype, "write", documentWriteDescriptor);
const documentWritelnDescriptor = Object.getOwnPropertyDescriptor(wrappedWindow.HTMLDocument.prototype, "writeln");
const documentWriteln = documentWritelnDescriptor.value;
documentWritelnDescriptor.value = exportFunction(function writeln(str){
const parts = str.split(/(?=>)/);
const length = parts.length - 1;
for (let i = 0; i < length; i += 1){
documentWrite.call(this, parts[i]);
allCallback();
}
documentWriteln.call(this, parts[length]);
}, window);
Object.defineProperty(wrappedWindow.HTMLDocument.prototype, "writeln", documentWritelnDescriptor);
};
}());

View File

@ -58,6 +58,7 @@
"lib/lists.js",
"lib/check.js",
"lib/iframeProtection.js",
"lib/frame.js"
]

View File

@ -27,6 +27,7 @@ Version 0.5.9:
- preventing double interception (increased performance and reduced detectability)
- detection over navigator and DOMRect getters was possible
- audio cache could break readout
- improved iFrame protection
known issues:
- if a data URL is blocked the page action button does not appear