Revision 97b8e30f
Added by jahoti about 2 years ago
| background/policy_injector.js | ||
|---|---|---|
| 12 | 12 |
* IMPORT get_storage |
| 13 | 13 |
* IMPORT browser |
| 14 | 14 |
* IMPORT is_chrome |
| 15 |
* IMPORT is_mozilla |
|
| 15 | 16 |
* IMPORT gen_unique |
| 16 | 17 |
* IMPORT gen_nonce |
| 17 | 18 |
* IMPORT is_privileged_url |
| ... | ... | |
| 39 | 40 |
"prefetch-src": true |
| 40 | 41 |
}; |
| 41 | 42 |
|
| 42 |
const header_name = "content-security-policy";
|
|
| 43 |
const report_only = "content-security-policy-report-only";
|
|
| 43 | 44 |
|
| 44 | 45 |
function not_csp_header(header) |
| 45 | 46 |
{
|
| ... | ... | |
| 82 | 83 |
}; |
| 83 | 84 |
} |
| 84 | 85 |
|
| 86 |
function process_csp_header(header, rule, block) |
|
| 87 |
{
|
|
| 88 |
const csp = parse_csp(header.value); |
|
| 89 |
|
|
| 90 |
/* No snitching */ |
|
| 91 |
delete csp['report-to']; |
|
| 92 |
delete csp['report-uri']; |
|
| 93 |
|
|
| 94 |
if (block) {
|
|
| 95 |
delete csp['script-src']; |
|
| 96 |
delete csp['script-src-elem']; |
|
| 97 |
csp['script-src-attr'] = ["'none'"]; |
|
| 98 |
csp['prefetch-src'] = ["'none'"]; |
|
| 99 |
} |
|
| 100 |
|
|
| 101 |
if ('script-src' in csp)
|
|
| 102 |
csp['script-src'].push(rule); |
|
| 103 |
else |
|
| 104 |
csp['script-src'] = [rule]; |
|
| 105 |
|
|
| 106 |
if ('script-src-elem' in csp)
|
|
| 107 |
csp['script-src-elem'].push(rule); |
|
| 108 |
else |
|
| 109 |
csp['script-src-elem'] = [rule]; |
|
| 110 |
|
|
| 111 |
const new_policy = Object.entries(csp).map( |
|
| 112 |
i => i[0] + ' ' + i[1].join(' ') + ';'
|
|
| 113 |
); |
|
| 114 |
|
|
| 115 |
return {name: header.name, value: new_policy.join('')}
|
|
| 116 |
} |
|
| 117 |
|
|
| 85 | 118 |
function headers_inject(details) |
| 86 | 119 |
{
|
| 87 | 120 |
const targets = url_extract_target(details.url); |
| ... | ... | |
| 89 | 122 |
if (!targets.current) |
| 90 | 123 |
return {cancel: true};
|
| 91 | 124 |
|
| 92 |
const headers = []; |
|
| 125 |
let orig_csp_headers = is_chrome ? null : []; |
|
| 126 |
let headers = []; |
|
| 127 |
let csp_headers = is_chrome ? headers : []; |
|
| 128 |
|
|
| 129 |
const rule = `'nonce-${targets.policy.nonce}'`;
|
|
| 130 |
const block = !targets.policy.allow; |
|
| 131 |
|
|
| 93 | 132 |
for (let header of details.responseHeaders) {
|
| 94 | 133 |
if (not_csp_header(header)) {
|
| 95 | 134 |
/* Retain all non-snitching headers */ |
| 96 |
if (header.name.toLowerCase() !== |
|
| 97 |
'content-security-policy-report-only') |
|
| 135 |
if (header.name.toLowerCase() !== report_only) {
|
|
| 98 | 136 |
headers.push(header); |
| 137 |
|
|
| 138 |
/* If these are the original CSP headers, use them instead */ |
|
| 139 |
/* Test based on url_extract_target() in misc.js */ |
|
| 140 |
if (is_mozilla && header.name === "x-orig-csp") {
|
|
| 141 |
let index = header.value.indexOf('%5B');
|
|
| 142 |
if (index === -1) |
|
| 143 |
continue; |
|
| 144 |
|
|
| 145 |
let sig = header.value.substring(0, index); |
|
| 146 |
let data = header.value.substring(index); |
|
| 147 |
if (sig !== sign_policy(data, 0)) |
|
| 148 |
continue; |
|
| 149 |
|
|
| 150 |
/* Confirmed- it's the originals, smuggled in! */ |
|
| 151 |
try {
|
|
| 152 |
data = JSON.parse(decodeURIComponent(data)); |
|
| 153 |
} catch (e) {
|
|
| 154 |
/* This should not be reached - |
|
| 155 |
it's our self-produced valid JSON. */ |
|
| 156 |
console.log("Unexpected internal error - invalid JSON smuggled!", e);
|
|
| 157 |
} |
|
| 158 |
|
|
| 159 |
orig_csp_headers = csp_headers = null; |
|
| 160 |
for (let header of data) |
|
| 161 |
headers.push(process_csp_header(header, rule, block)); |
|
| 162 |
} |
|
| 163 |
} |
|
| 99 | 164 |
|
| 100 | 165 |
continue; |
| 101 | 166 |
} |
| 102 |
|
|
| 103 |
const csp = parse_csp(header.value); |
|
| 104 |
const rule = `'nonce-${targets.policy.nonce}'`
|
|
| 105 |
|
|
| 106 |
/* TODO: confirm deleting non-existent things is OK everywhere */ |
|
| 107 |
/* No snitching or prefetching/rendering */ |
|
| 108 |
delete csp['report-to']; |
|
| 109 |
delete csp['report-uri']; |
|
| 110 |
|
|
| 111 |
if (!targets.policy.allow) {
|
|
| 112 |
delete csp['script-src']; |
|
| 113 |
delete csp['script-src-elem']; |
|
| 114 |
csp['script-src-attr'] = ["'none'"]; |
|
| 115 |
csp['prefetch-src'] = ["'none'"]; |
|
| 116 |
} |
|
| 117 |
|
|
| 118 |
if ('script-src' in csp)
|
|
| 119 |
csp['script-src'].push(rule); |
|
| 120 |
else |
|
| 121 |
csp['script-src'] = [rule]; |
|
| 122 |
|
|
| 123 |
if ('script-src-elem' in csp)
|
|
| 124 |
csp['script-src-elem'].push(rule); |
|
| 125 |
else |
|
| 126 |
csp['script-src-elem'] = [rule]; |
|
| 127 |
|
|
| 128 |
/* TODO: is this safe */ |
|
| 129 |
let new_policy = Object.entries(csp).map( |
|
| 130 |
i => i[0] + ' ' + i[1].join(' ') + ';'
|
|
| 131 |
); |
|
| 167 |
if (is_mozilla && !orig_csp_headers) |
|
| 168 |
continue; |
|
| 132 | 169 |
|
| 133 |
headers.push({name: header.name, value: new_policy.join('')});
|
|
| 170 |
csp_headers.push(process_csp_header(header, rule, block)); |
|
| 171 |
if (is_mozilla) |
|
| 172 |
orig_csp_headers.push(header); |
|
| 173 |
} |
|
| 174 |
|
|
| 175 |
if (orig_csp_headers) {
|
|
| 176 |
/** Smuggle in the original CSP headers for future use. |
|
| 177 |
* These are signed with a time of 0, as it's not clear there |
|
| 178 |
* is a limit on how long Firefox might retain these headers in |
|
| 179 |
* the cache. |
|
| 180 |
*/ |
|
| 181 |
orig_csp_headers = encodeURIComponent(JSON.stringify(orig_csp_headers)); |
|
| 182 |
headers.push({
|
|
| 183 |
name: "x-orig-csp", |
|
| 184 |
value: sign_policy(orig_csp_headers, 0) + orig_csp_headers |
|
| 185 |
}); |
|
| 186 |
|
|
| 187 |
headers = headers.concat(csp_headers); |
|
| 188 |
} |
|
| 189 |
|
|
| 190 |
/* To ensure there is a CSP header if required */ |
|
| 191 |
if (block) {
|
|
| 192 |
headers.push({
|
|
| 193 |
name: "content-security-policy", |
|
| 194 |
value: `script-src ${rule}; script-src-elem ${rule}; ` +
|
|
| 195 |
"script-src-attr 'none'; prefetch-src 'none';" |
|
| 196 |
}); |
|
| 134 | 197 |
} |
| 135 | 198 |
|
| 136 | 199 |
return {responseHeaders: headers};
|
Also available in: Unified diff
Squash more CSP-filtering bugs
On Firefox, original CSP headers are now smuggled (signed) in an x-orig-csp
header to prevent re-processing issues with caching. Additionally, a default
header is added for non-whitelisted domains in case there are no existing
headers we can attach to.