Project

General

Profile

« Previous | Next » 

Revision 5dab077b

Added by jahoti about 2 years ago

Replace CSP filtering with blocking

CSP headers are now blocked completely rather than modified.
Also, filtering is applied whenever a payload is injected.

View differences:

background/policy_injector.js
11 11
 * IMPORT sign_data
12 12
 * IMPORT extract_signed
13 13
 * IMPORT sanitize_csp_header
14
 * IMPORT csp_rule
14
 * IMPORT make_csp_rule
15 15
 * IMPORT is_csp_header_name
16 16
 * IMPORTS_END
17 17
 */
18 18

  
19 19
function inject_csp_headers(headers, policy)
20 20
{
21
    let csp_headers;
22
    let old_signature;
23
    let hachette_header;
21
    if (!policy.allow || policy.has_payload) {
22
	/* Remove report-only CSP headers that snitch on us. */
23
	headers = headers.filter(h => !is_csp_header_name(h.name, true));
24 24

  
25
    for (const header of headers.filter(h => h.name === "x-hachette")) {
26
	/* x-hachette header has format: <signature>_0_<data> */
27
	const match = /^([^_]+)_(0_.*)$/.exec(header.value);
28
	if (!match)
29
	    continue;
30

  
31
	const result = extract_signed(...match.slice(1, 3));
32
	if (result.fail)
33
	    continue;
34

  
35
	/* This should succeed - it's our self-produced valid JSON. */
36
	const old_data = JSON.parse(decodeURIComponent(result.data));
37

  
38
	/* Confirmed- it's the originals, smuggled in! */
39
	csp_headers = old_data.csp_headers;
40
	old_signature = old_data.policy_sig;
41

  
42
	hachette_header = header;
43
	break;
44
    }
45

  
46
    if (!hachette_header) {
47
	hachette_header = {name: "x-hachette"};
48
	headers.push(hachette_header);
25
	/* Add our own CSP header */
26
	headers.push({
27
	    name: "content-security-policy",
28
	    value: make_csp_rule(policy)
29
	});
49 30
    }
50

  
51
    csp_headers = csp_headers ||
52
	headers.filter(h => is_csp_header_name(h.name));
53

  
54
    /* When blocking remove report-only CSP headers that snitch on us. */
55
    headers = headers.filter(h => !is_csp_header_name(h.name, !policy.allow));
56

  
57
    if (old_signature)
58
	headers = headers.filter(h => h.value.search(old_signature) === -1);
59

  
60
    headers.push(...csp_headers.map(h => sanitize_csp_header(h, policy)));
61

  
31
    
62 32
    const policy_str = encodeURIComponent(JSON.stringify(policy));
63 33
    const signed_policy = sign_data(policy_str, new Date().getTime());
64 34
    const later_30sec = new Date(new Date().getTime() + 30000).toGMTString();
......
67 37
	value: `hachette-${signed_policy.join("=")}; Expires=${later_30sec};`
68 38
    });
69 39

  
70
    /*
71
     * Smuggle in the signature and the original CSP headers for future use.
72
     * These are signed with a time of 0, as it's not clear there is a limit on
73
     * how long Firefox might retain headers in the cache.
74
     */
75
    let hachette_data = {csp_headers, policy_sig: signed_policy[0]};
76
    hachette_data = encodeURIComponent(JSON.stringify(hachette_data));
77
    hachette_header.value = sign_data(hachette_data, 0).join("_");
78

  
79
    /* To ensure there is a CSP header if required */
80
    if (!policy.allow)
81
	headers.push({
82
	    name: "content-security-policy",
83
	    value: csp_rule(policy.nonce)
84
	});
85

  
86 40
    return headers;
87 41
}
88 42

  
common/misc.js
146 146
    return {name: header.name, value: new_csp.join('')};
147 147
}
148 148

  
149
/* csp rule that blocks all scripts except for those injected by us */
150
function make_csp_rule(policy)
151
{
152
    let rule = "prefetch-src 'none'; ", nonce = `'nonce-${policy.nonce}'`;
153
    if (!policy.allow) {
154
	rule += `script-src ${nonce}; script-src-elem ${nonce}; ` +
155
	    "script-src-attr 'none'; ";
156
    }
157
    return rule;
158
}
159

  
149 160
/* Regexes and objects to use as/in schemas for parse_json_with_schema(). */
150 161
const nonempty_string_matcher = /.+/;
151 162

  
......
161 172
/*
162 173
 * EXPORTS_START
163 174
 * EXPORT gen_nonce
164
 * EXPORT csp_rule
175
 * EXPORT make_csp_rule
165 176
 * EXPORT is_csp_header_name
166 177
 * EXPORT nice_name
167 178
 * EXPORT open_in_settings
content/main.js
17 17
 * IMPORT is_chrome
18 18
 * IMPORT is_mozilla
19 19
 * IMPORT start_activity_info_server
20
 * IMPORT csp_rule
20
 * IMPORT make_csp_rule
21 21
 * IMPORT is_csp_header_name
22 22
 * IMPORT sanitize_csp_header
23 23
 * IMPORTS_END
......
175 175
	return;
176 176

  
177 177
    block_attribute(meta, "content");
178

  
179
    if (is_csp_header_name(http_equiv, false))
180
	meta.content = sanitize_csp_header({value}, policy).value;
181 178
}
182 179

  
183 180
function sanitize_script(script)
......
204 201
{
205 202
    const meta = doc.createElement("meta");
206 203
    meta.setAttribute("http-equiv", "Content-Security-Policy");
207
    meta.setAttribute("content", csp_rule(policy.nonce));
204
    meta.setAttribute("content", make_csp_rule(policy));
208 205
    doc.head.append(meta);
209 206
    /* CSP is already in effect, we can remove the <meta> now. */
210 207
    meta.remove();
......
240 237
    for (const meta of old_html.querySelectorAll("head meta"))
241 238
	sanitize_meta(meta, policy);
242 239

  
243
    for (const script of old_html.querySelectorAll("script"))
244
	sanitize_script(script, policy);
240
    if (!policy.allow)
241
	for (const script of old_html.querySelectorAll("script"))
242
	     sanitize_script(script, policy);
245 243

  
246 244
    new_html.replaceWith(old_html);
247 245

  
248
    for (const script of old_html.querySelectorAll("script"))
249
	desanitize_script(script, policy);
246
    if (!policy.allow)
247
	for (const script of old_html.querySelectorAll("script"))
248
	    desanitize_script(script, policy);
250 249
}
251 250

  
252 251
if (!is_privileged_url(document.URL)) {
......
282 281
    }
283 282

  
284 283
    const doc_ready = Promise.all([
285
	policy.allow ? Promise.resolve : sanitize_document(document, policy),
284
	(policy.allow && !policy.has_payload) ? Promise.resolve : sanitize_document(document, policy),
286 285
	new Promise(cb => document.addEventListener("DOMContentLoaded",
287 286
						    cb, {once: true}))
288 287
    ]);

Also available in: Unified diff