2

I am creating a privacy extension that runs a content script on document_start.

The content script needs to inject a script with a dynamic value for each different origin e.g. google.com, twitter.com etc etc.

This is my content script:

console.log("Content Script Running ...");
console.log("Window origin: " + window.location.href);

function inject(filePath) {
  var script = document.createElement('script');
  script.src = chrome.extension.getURL(filePath);
  script.onload = function() {
    this.remove();
  };
  (document.head || document.documentElement).appendChild(script);
}

function injectText(text) {
  var script = document.createElement('script');
  script.textContent = text;
  script.onload = function() {
    this.remove();
  };
  (document.head || document.documentElement).appendChild(script);
}

function getSeed(origin) {
    // Get a Storage object
    var storage = window.sessionStorage;

    // Do we already have a seed in storage for this origin or not?
    var seed = storage.getItem(origin);

    if (seed === null) {
        // Initialise a 32 byte buffer
        seed = new Uint8Array(32);

        // Fill it with cryptographically random values
        window.crypto.getRandomValues(seed);

        // Save it to storage
        storage.setItem(origin, seed);
    }

    return seed;
}

var origin = window.location.hostname;

var seed = getSeed(origin);

injectText("var seed = '" + seed + "';");
console.log("[INFO] Injected Seed ...");

inject("js/lib/seedrandom.min.js");
console.log("[INFO] Injected Seed Random ...");

inject("js/random.js");
console.log("[INFO] Injected Random ...");

inject("js/api/document.js");
console.log("[INFO] Injected Document API ...");

inject("js/api/navigator.js");
console.log("[INFO] Injected Navigator API ...");

inject("js/api/canvas.js");
console.log("[INFO] Injected Canvas API ...");

inject("js/api/history.js");
console.log("[INFO] Injected History API ...");

inject("js/api/battery.js");
console.log("[INFO] Injected Battery API ...");

inject("js/api/audio.js");
console.log("[INFO] Injected Audio API ...");

inject("js/api/element.js");
console.log("[INFO] Injected Element API ...");

When trying to run this extension on a website with a strict CSP e.g. github.com, my script with a dynamic seed value is blocked and my other scripts which depend on that value end up referencing an undefined value. Any ideas how I can get around this.

The scripts loaded using the src attribute are ok since they are in a .js file and are loaded from the extension however that one script that has a dynamic value aka var seed = ... is blocked because it is injected using the textContent attribute.

I need to have this code run syncronously and before any other script on the page run hence why i have the content script run on document_start.

Any ideas?

  • Modify CSP header via chrome.webRequest API. – wOxxOm Aug 19 '17 at 03:52
  • 1
    OK. Wouldn't this create a possible security issue for the site? Or is there a way to do this safely? I don't want my addon to be known as "The addon that messes with websites CSP". Maybe I'm over reacting. –  Aug 19 '17 at 03:55
  • 1
    Calculate sha256 of your injected code (e.g. via built-in crypto API) and use it in your modified CSP. – wOxxOm Aug 19 '17 at 04:14
  • I can't. The code changes each time for each page. E.g. for google.com it would be "var seed = 'value0';" then for another site it could be 'var seed = "value1";' I could maybe use the nonce feature? and give my inline script a nonce perhaps? –  Aug 19 '17 at 04:16
  • If you can generate that code at the time webRequest listener runs, you certainly can calculate sha256 right away. – wOxxOm Aug 19 '17 at 04:23
  • That code is generated in the content script not the background script where the webRequest code is (I haven't posted the background script). I could use chrome.tabs.executeScript() in background page to inject content script with dynamic value then use that value and inject it again into the page however I have had timing issues with executeScript() manifest document_start is more reliable. Anyway thanks for your help you have given me some good ideas. –  Aug 19 '17 at 04:27
  • You can do synchronous communication from the content script to the background page via sync XHR (it'll display a deprecation warning in the console) which is intercepted with webRequest. The problem we'll be the timing, though: the content script runs after webRequest.onHeadersReceived. I guess you'll have to rework your extension to generate the injected code in bg. See also: [sync communication from bg to content script via cookies](https://stackoverflow.com/a/45105934) (in case of long strings use blob urls i.e. createObjectURL in bg and sync XHR in the content script) – wOxxOm Aug 19 '17 at 06:59
  • 1
    Why do you need to transfer this data inserted as a ` – Makyen Aug 19 '17 at 07:00

1 Answers1

3

I fixed the issue. The main issue I was having was trying to inject an inline text script which had the following content:

var seed = $(value that changes depending on the page)

This gets blocked by certain websites such as twitter.com and github.com which have restrictive content security policies.

My solution was to do the following in my content script:

var filePath = // Get filepath to script
var seed = // Get seed value in content script

var script = document.createElement('script');
script.setAttribute("data-seed", seed);
script.src = chrome.extension.getURL(filePath);
script.onload = function() {
  this.remove();
};
(document.head || document.documentElement).appendChild(script);

This will create a script in the page like so

<script data-seed="$(DATA-SEED-VALUE)" src="$(SRC-VALUE)"></script>

Then from within this script which is now running as a page script (in the content of the web page):

var seed = document.currentScript.getAttribute("data-seed");

Which gets the seed. This solution is much neater, easier and doesn't require altering CSP which could create security issues for the site you are interacting with.