Skip to content

CSP bypass #954

Description

@ACTCD

We have had some CSP related issues like:

We mentioned and considered this issue again in discussion #945.

I'm a bit unsure and don't fully remember why we previously thought bypassing CSP was impossible.

I guess the main reason was that we cannot use tab.executeScript() to inject scripts at an accurate earlier time and dose not supports injection into the page scope, and we didn’t pay enough attention to the new MV3 API scripting.executeScript() (also available with MV2 in Safari)?

Anyway, I mentioned other day that actually we could combine the two, although it might add a bit of async latency, but bypassing CSP seems possible. Specifically we can ensure timing through content scripts and inject actual user script through scripting.executeScript().

I did a simple test and in a page with the script-src 'none' policy, injecting an inline script will get the error:

// ==UserScript==
// @name               DEBUG.CSP
// @license            GPL-3.0-or-later
// @match              *://*.example.com/*
// @inject-into        content
// @weight             999
// ==/UserScript==

(function () {
	"use strict";

	const meta = document.createElement("meta");
	meta.setAttribute("http-equiv", "Content-Security-Policy");
	meta.setAttribute("content", "script-src 'none'");
	document.head.append(meta);

	const script = document.createElement("script");
	script.textContent = `console.log(111)`;
	document.body.append(script);
})();
Refused to execute a script because its hash, its nonce, or 'unsafe-inline' does not appear in the script-src directive of the Content Security Policy.

Note

It should be noted that there is no distinction between page context and content script context here, cause inline scripts are always appended to the DOM.

Then in the same page, executing from the background page, it succeeds:

tab = await browser.tabs.getCurrent();
await browser.scripting.executeScript({
	target: { tabId: tab.id },
	world: "MAIN",
	func: () => {
		window.somePageVariable = 222;
		console.log(window.somePageVariable);
		return 333;
	},
});

Note

Note that in Safari the injectImmediately parameter is always true.
"Scripts are always injected immediately."

So we just need to wrap and implement the @run-at logic additionally.

And since scripting.executeScript() does not modify the DOM, this has some additional benefits.

So there doesn't seem to be a significant impediment other than the impact on @run-at document-start of possible additional delays due to asynchronous messaging and execution.

Finally, from what I remember reading, the MV3 userScripts API should also support bypassing CSP.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions