Project

General

Profile

« Previous | Next » 

Revision 57b80d72

Added by jahoti about 2 years ago

[UNTESTED- will test] Use more nuanced CSP filtering

CSP headers are now parsed and processed, rather than treated as simple
units. This allows us to ensure policies delivered as HTTP headers do not
interfere with our script filtering, as well as to preserve useful protections
while removing the ones that could be problematic. Additionally, prefetching
should now be blocked on pages where native scripts aren't allowed, and
all reporting of CSP violations has been stripped (is this appropriate?).

View differences:

background/policy_injector.js
12 12
 * IMPORT get_storage
13 13
 * IMPORT browser
14 14
 * IMPORT is_chrome
15
 * IMPORT is_mozilla
16 15
 * IMPORT gen_unique
17 16
 * IMPORT gen_nonce
18 17
 * IMPORT is_privileged_url
19 18
 * IMPORT url_extract_target
20 19
 * IMPORT sign_policy
21 20
 * IMPORT get_query_best
22
 * IMPORT csp_rule
21
 * IMPORT parse_csp
23 22
 * IMPORTS_END
24 23
 */
25 24

  
......
32 31
    "x-content-security-policy" : true
33 32
};
34 33

  
34
const unwanted_csp_directives = {
35
    "report-to" : true,
36
    "report-uri" : true,
37
    "script-src" : true,
38
    "script-src-elem" : true,
39
    "prefetch-src": true
40
};
41

  
35 42
const header_name = "content-security-policy";
36 43

  
37
function is_csp_header(header)
44
function not_csp_header(header)
38 45
{
39
    return !!csp_header_names[header.name.toLowerCase()];
46
    return !csp_header_names[header.name.toLowerCase()];
40 47
}
41 48

  
42 49
function url_inject(details)
......
82 89
    if (!targets.current)
83 90
	return {cancel: true};
84 91

  
85
    const rule = csp_rule(targets.policy.nonce);
86
    var headers = details.responseHeaders;
87

  
88
    /*
89
     * Chrome doesn't have the buggy behavior of caching headers
90
     * we injected. Firefox does and we have to remove it there.
91
     */
92
    if (!targets.policy.allow || is_mozilla)
93
	headers = headers.filter(h => !is_csp_header(h));
94

  
95
    if (!targets.policy.allow) {
96
	headers.push({
97
	    name : header_name,
98
	    value : rule
99
	});
92
    const headers = [];
93
    for (let header of details.responseHeaders) {
94
	if (not_csp_header(header)) {
95
	    /* Retain all non-snitching headers */
96
	    if (header.name.toLowerCase() !==
97
	        'content-security-policy-report-only')
98
		headers.push(header);
99

  
100
	    continue;
101
	}
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 (!target.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
	);
132
	
133
	headers.push({name: header.name, value: new_policy.join('')});
100 134
    }
101 135

  
102 136
    return {responseHeaders: headers};

Also available in: Unified diff