I am trying to set a javascript variable to identify that a Firefox extension is installed. The idea is to read this variable on the page.
After failing to implement this using the complicated approaches when using XUL add-ons (Set an object in a page's window object from a Firefox extension?) and finding out about contentScripts in Firefox Add On SDK which looks suitable for the task, I am still having problems accessing javascript variables set in the contentScript on a page.
I have the following in my main.js (using the sample provided here https://addons.mozilla.org/en-US/developers/docs/sdk/latest/modules/sdk/page-mod.html):
var pageMod = require("sdk/page-mod");
pageMod.PageMod({
include: "*",
contentScript: 'window.isInstalled=true;window.alert("Page matches ruleset");',
contentScriptWhen: "ready"
});
However when I try to get the window.isInstalled
variable in javascript on a page, it is undefined, despite the alert was shown.
How to ensure that the value written by the content script will be available on a page?
The window
object in content scripts is generally an XRayWrapper
, meaning that you'll get the original object back instead of whatever the website may have overwritten. That in turn also means, that the website won't see any modifications you make to the window
object.
However, unsafeWindow.isInstalled = ...
should work (although .isInstalled
is very generic and may therefore clash with variables a website already uses).
See the docs regarding XRayWrapper
and unsafeWindow
.
Addressing the very valid concerns raised by @canuckistani:
Reading data from or executing functions of unsafeWindow
is safe in the sense that it cannot directly lead to code execution in another (your content script) compartment. Spidermonkey's compartments will make sure of that. Everything else would be a security issue within either Spidermonkey or Gecko. (Please note that this refers to Spidermonkey only. IIRC it is the only engine to implement compartments or membranes or whatever you'd like to call it. Other engines might have other security properties).
But it is very true that you must never trust data coming from a website. unsafeWindow
just have more ways to get you pwnd.
Always except code to throw, Denial-of-service you with an unexpected infinite loop or similar and never, ever explicitly or implicitly evaluate code in your context.
E.g. this is a security vulnerability, no matter if you use window or unsafeWindow:
for (var el of window.document.querySelectorAll("*[onclick]")) {
el.addEventListener("click", el.getAttribute("onclick"));
el.removeAttribute("onclick");
}
This would create anonymous functions (the event listeners) from website-controlled strings (.getAttribute
) within your script context, no the context of a website.
This would be unsafe as well, but only when using unsafeWindow
this time:
for (var el of unsafeWindow.document.querySelectorAll("p")) {
el.addEventListener("click", 'alert("I am ' + el.clientHeight + 'px tall");');
el.removeAttribute("onclick");
}
When using the XRayWrapper
wrapped window
, you can be sure that document
is actually the document, document.querySelectorAll
is actually a function spitting out some Elements and el.clientHeight
is actually a numeric value.
Going through unsafeWindow
all these assumptions may be incorrect. The website might have done something like this:
document.querySelectorAll = function() {
return [{
clientHeight: 'a pwnd content script"); doSomethingEvil(); alert("Now I own you! And I am certainly not 0'
}];
};
While it is still safe in a privilege-escalation sense to execute unsafeWindow.document.querySelectorAll
, as the overridden function would still run within the compartment (security context) of the website, the return value cannot be trusted at all.
Or the script might have done other things to break your stuff, not necessarily with malicious intent. E.g. your off-the-shell Denial-of-Service.
Object.defineProperty(document, "title", {
get: function() { while(true); }
});
// or
Object.defineProperty(document, "title", {
get: function() { throw new Error("get off my lawn!"); }
});