More accurate blockage reporting, with better filtering of page's own CSP effects.
This commit is contained in:
parent
51dadae00a
commit
c22ce43f01
|
@ -176,9 +176,10 @@ var RequestGuard = (() => {
|
||||||
let tabId = sender.tab.id;
|
let tabId = sender.tab.id;
|
||||||
let {frameId} = sender;
|
let {frameId} = sender;
|
||||||
let r = {
|
let r = {
|
||||||
url, type, tabId, frameId
|
url, type, tabId, frameId, documentUrl: sender.url
|
||||||
};
|
};
|
||||||
Content.reportTo(r, false, policyTypesMap[type]);
|
Content.reportTo(r, false, policyTypesMap[type]);
|
||||||
|
debug("Violation", type, url, tabId, frameId);
|
||||||
if (type === "script" && url === sender.url) {
|
if (type === "script" && url === sender.url) {
|
||||||
TabStatus.record(r, "noscriptFrame", true);
|
TabStatus.record(r, "noscriptFrame", true);
|
||||||
} else {
|
} else {
|
||||||
|
@ -539,7 +540,7 @@ var RequestGuard = (() => {
|
||||||
if (!/:/.test(r.url)) r.url = request.documentUrl;
|
if (!/:/.test(r.url)) r.url = request.documentUrl;
|
||||||
Content.reportTo(r, false, policyTypesMap[r.type]);
|
Content.reportTo(r, false, policyTypesMap[r.type]);
|
||||||
TabStatus.record(r, "blocked");
|
TabStatus.record(r, "blocked");
|
||||||
} else if (report["violated-directive"] === "script-src" && (originalPolicy.includes("; script-src 'none'"))) {
|
} else if (report["violated-directive"].startsWith("script-src") && (originalPolicy.includes("script-src 'none'"))) {
|
||||||
let r = fakeRequestFromCSP(report, request);
|
let r = fakeRequestFromCSP(report, request);
|
||||||
Content.reportTo(r, false, "script"); // NEW
|
Content.reportTo(r, false, "script"); // NEW
|
||||||
TabStatus.record(r, "noscriptFrame", true);
|
TabStatus.record(r, "noscriptFrame", true);
|
||||||
|
|
|
@ -8,10 +8,12 @@ function CapsCSP(baseCSP = new CSP()) {
|
||||||
let forbidData = new Set(this.dataUriTypes.filter(t => !capabilities.has(t)));
|
let forbidData = new Set(this.dataUriTypes.filter(t => !capabilities.has(t)));
|
||||||
let blockedTypes = new Set(this.types.filter(t => !capabilities.has(t)));
|
let blockedTypes = new Set(this.types.filter(t => !capabilities.has(t)));
|
||||||
if(!capabilities.has("script")) {
|
if(!capabilities.has("script")) {
|
||||||
|
blockedTypes.add({name: "script-src-elem"});
|
||||||
|
blockedTypes.add({name: "script-src-attr"});
|
||||||
blockedTypes.add("worker");
|
blockedTypes.add("worker");
|
||||||
if (!blockedTypes.has("object")) {
|
if (!blockedTypes.has("object")) {
|
||||||
// data: URIs loaded in objects may run scripts
|
// data: URIs loaded in objects may run scripts
|
||||||
blockedTypes.add({name: "object", value: "http:"});
|
blockedTypes.add({type: "object", value: "http:"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,7 +22,7 @@ function CapsCSP(baseCSP = new CSP()) {
|
||||||
// for instance data: and blob: URIs
|
// for instance data: and blob: URIs
|
||||||
for (let type of this.dataUriTypes) {
|
for (let type of this.dataUriTypes) {
|
||||||
if (blockedTypes.delete(type)) {
|
if (blockedTypes.delete(type)) {
|
||||||
blockedTypes.add({name: type, value: "http:"});
|
blockedTypes.add({type, value: "http:"});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ class DocumentCSP {
|
||||||
|
|
||||||
let csp = this.builder;
|
let csp = this.builder;
|
||||||
let blocker = csp.buildFromCapabilities(capabilities, embedding);
|
let blocker = csp.buildFromCapabilities(capabilities, embedding);
|
||||||
if (!blocker) return true;
|
if (!blocker) return null;
|
||||||
|
|
||||||
let createHTMLElement =
|
let createHTMLElement =
|
||||||
tagName => document.createElementNS("http://www.w3.org/1999/xhtml", tagName);
|
tagName => document.createElementNS("http://www.w3.org/1999/xhtml", tagName);
|
||||||
|
@ -34,7 +34,7 @@ class DocumentCSP {
|
||||||
try {
|
try {
|
||||||
if (!(document instanceof HTMLDocument)) {
|
if (!(document instanceof HTMLDocument)) {
|
||||||
if (!(document instanceof XMLDocument)) {
|
if (!(document instanceof XMLDocument)) {
|
||||||
return false; // nothing to do with ImageDocument, for instance
|
return null; // nothing to do with ImageDocument, for instance
|
||||||
}
|
}
|
||||||
// non-HTML XML documents ignore <meta> CSP unless wrapped in
|
// non-HTML XML documents ignore <meta> CSP unless wrapped in
|
||||||
// - <html><head></head></head> on Gecko
|
// - <html><head></head></head> on Gecko
|
||||||
|
@ -62,8 +62,8 @@ class DocumentCSP {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
error(e, "Error inserting CSP %s in %s", document.URL, header && header.value);
|
error(e, "Error inserting CSP %s in %s", document.URL, header && header.value);
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
return true;
|
return CSP.normalize(header.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,23 +111,42 @@ var notifyPage = async () => {
|
||||||
window.addEventListener("pageshow", notifyPage);
|
window.addEventListener("pageshow", notifyPage);
|
||||||
|
|
||||||
let violations = new Set();
|
let violations = new Set();
|
||||||
|
let documentOrigin = new URL(document.URL).origin;
|
||||||
window.addEventListener("securitypolicyviolation", e => {
|
window.addEventListener("securitypolicyviolation", e => {
|
||||||
if (!e.isTrusted) return;
|
if (!e.isTrusted) return;
|
||||||
let {violatedDirective, originalPolicy} = e;
|
let {violatedDirective, originalPolicy} = e;
|
||||||
if (violatedDirective === `script-src 'none'`) onScriptDisabled();
|
if (violatedDirective.startsWith(`script-src`) && originalPolicy.includes("script-src 'none'")) onScriptDisabled();
|
||||||
|
|
||||||
let type = violatedDirective.split("-", 1)[0]; // e.g. script-src 'none' => script
|
let type = violatedDirective.split("-", 1)[0]; // e.g. script-src 'none' => script
|
||||||
let url = e.blockedURI;
|
let url = e.blockedURI;
|
||||||
if (type === "media" && /^data\b/.test(url) && (!CSP.isMediaBlocker(originalPolicy) ||
|
if (type === "media" && /^data\b/.test(url) && (!CSP.isMediaBlocker(originalPolicy) ||
|
||||||
ns.embeddingDocument || !document.querySelector("video,audio")))
|
ns.embeddingDocument || !document.querySelector("video,audio")))
|
||||||
return;
|
return;
|
||||||
if (!(url && url.includes(":"))) {
|
if (!ns.CSP || !(CSP.normalize(originalPolicy).includes(ns.CSP))) {
|
||||||
url = document.URL;
|
// this seems to come from page's own CSP
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
let key = type + "@" + url;
|
let documentUrl = document.URL;
|
||||||
|
let origin;
|
||||||
|
if (!(url && url.includes(":"))) {
|
||||||
|
url = documentUrl;
|
||||||
|
origin = documentOrigin;
|
||||||
|
} else {
|
||||||
|
({origin} = new URL(url));
|
||||||
|
}
|
||||||
|
let key = RequestKey.create(origin, type, documentOrigin);
|
||||||
if (violations.has(key)) return;
|
if (violations.has(key)) return;
|
||||||
violations.add(key);
|
violations.add(key);
|
||||||
if (type === "frame") type = "sub_frame";
|
if (type === "frame") type = "sub_frame";
|
||||||
|
seen.record({
|
||||||
|
request: {
|
||||||
|
key,
|
||||||
|
url,
|
||||||
|
type,
|
||||||
|
documentUrl,
|
||||||
|
},
|
||||||
|
allowed: false
|
||||||
|
});
|
||||||
Messages.send("violation", {url, type});
|
Messages.send("violation", {url, type});
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
function onScriptDisabled() {
|
function onScriptDisabled() {
|
||||||
if (document.readyState === "loading") {
|
if (document.readyState === "loading") {
|
||||||
|
if (!onScriptDisabled._installed) {
|
||||||
window.addEventListener("DOMContentLoaded", e => onScriptDisabled());
|
window.addEventListener("DOMContentLoaded", e => onScriptDisabled());
|
||||||
|
onScriptDisabled._installed = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onScriptDisabled = () => {};
|
onScriptDisabled = () => {};
|
||||||
|
|
|
@ -105,7 +105,7 @@
|
||||||
perms.capabilities.push("script");
|
perms.capabilities.push("script");
|
||||||
}
|
}
|
||||||
this.capabilities = new Set(perms.capabilities);
|
this.capabilities = new Set(perms.capabilities);
|
||||||
new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
|
this.CSP = new DocumentCSP(document).apply(this.capabilities, this.embeddingDocument);
|
||||||
}
|
}
|
||||||
this.canScript = this.allows("script");
|
this.canScript = this.allows("script");
|
||||||
this.fire("capabilities");
|
this.fire("capabilities");
|
||||||
|
|
|
@ -4,13 +4,16 @@ class CSP {
|
||||||
static isMediaBlocker(csp) {
|
static isMediaBlocker(csp) {
|
||||||
return /(?:^|[\s;])media-src (?:'none'|http:)(?:;|$)/.test(csp);
|
return /(?:^|[\s;])media-src (?:'none'|http:)(?:;|$)/.test(csp);
|
||||||
}
|
}
|
||||||
|
static normalize(csp) {
|
||||||
|
return csp.replace(/\s*;\s*/g, ';').replace(/\b(script-src\s+'none'.*?;)(?:script-src-\w+\s+'none';)+/, '$1');
|
||||||
|
}
|
||||||
|
|
||||||
build(...directives) {
|
build(...directives) {
|
||||||
return directives.join(';');
|
return directives.join(';');
|
||||||
}
|
}
|
||||||
|
|
||||||
buildBlocker(...types) {
|
buildBlocker(...types) {
|
||||||
return this.build(...(types.map(type => `${type.name || type}-src ${type.value || "'none'"}`)));
|
return this.build(...(types.map(t => `${t.name || `${t.type || t}-src`} ${t.value || "'none'"}`)));
|
||||||
}
|
}
|
||||||
|
|
||||||
blocks(header, type) {
|
blocks(header, type) {
|
||||||
|
|
|
@ -48,6 +48,7 @@
|
||||||
"lib/NetCSP.js",
|
"lib/NetCSP.js",
|
||||||
"lib/TabCache.js",
|
"lib/TabCache.js",
|
||||||
"common/CapsCSP.js",
|
"common/CapsCSP.js",
|
||||||
|
"common/RequestKey.js",
|
||||||
"common/Policy.js",
|
"common/Policy.js",
|
||||||
"common/locale.js",
|
"common/locale.js",
|
||||||
"common/Entities.js",
|
"common/Entities.js",
|
||||||
|
@ -97,6 +98,7 @@
|
||||||
"lib/Messages.js",
|
"lib/Messages.js",
|
||||||
"lib/CSP.js",
|
"lib/CSP.js",
|
||||||
"common/CapsCSP.js",
|
"common/CapsCSP.js",
|
||||||
|
"common/RequestKey.js",
|
||||||
"content/DocumentCSP.js",
|
"content/DocumentCSP.js",
|
||||||
"content/onScriptDisabled.js",
|
"content/onScriptDisabled.js",
|
||||||
"content/staticNS.js",
|
"content/staticNS.js",
|
||||||
|
|
Loading…
Reference in New Issue