Search code examples
javascriptfirefoxfirefox-addonfirefox-addon-sdk

How to set javascript variable in Firefox Add On created using the Add On SDK?


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?


Solution

  • 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!"); }
    });