1
|
# SPDX-License-Identifier: CC0-1.0
|
2
|
|
3
|
"""
|
4
|
Haketilo unit tests - enforcing script blocking policy from content script
|
5
|
"""
|
6
|
|
7
|
# This file is part of Haketilo
|
8
|
#
|
9
|
# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org>
|
10
|
#
|
11
|
# This program is free software: you can redistribute it and/or modify
|
12
|
# it under the terms of the CC0 1.0 Universal License as published by
|
13
|
# the Creative Commons Corporation.
|
14
|
#
|
15
|
# This program is distributed in the hope that it will be useful,
|
16
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
17
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
18
|
# CC0 1.0 Universal License for more details.
|
19
|
|
20
|
import pytest
|
21
|
import json
|
22
|
import urllib.parse
|
23
|
from selenium.webdriver.support.ui import WebDriverWait
|
24
|
|
25
|
from ..script_loader import load_script
|
26
|
from .utils import are_scripts_allowed
|
27
|
|
28
|
# For simplicity, we'll use one nonce in all test cases.
|
29
|
nonce = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'
|
30
|
|
31
|
allow_policy = {'allow': True}
|
32
|
block_policy = {
|
33
|
'allow': False,
|
34
|
'csp': f"prefetch-src 'none'; script-src-attr 'none'; script-src 'none'; script-src-elem 'none'; frame-src http://* https://*;"
|
35
|
}
|
36
|
payload_policy = {
|
37
|
'mapping': 'somemapping',
|
38
|
'payload': {'identifier': 'someresource'},
|
39
|
'csp': f"prefetch-src 'none'; script-src-attr 'none'; script-src 'nonce-{nonce}'; script-src-elem 'nonce-{nonce}';"
|
40
|
}
|
41
|
|
42
|
content_script = load_script('content/policy_enforcing.js') + ''';{
|
43
|
const smuggled_what_to_do = /^[^#]*#?(.*)$/.exec(document.URL)[1];
|
44
|
const what_to_do = smuggled_what_to_do === "" ? {policy: {allow: true}} :
|
45
|
JSON.parse(decodeURIComponent(smuggled_what_to_do));
|
46
|
|
47
|
if (what_to_do.csp_off) {
|
48
|
const orig_DOMParser = window.DOMParser;
|
49
|
window.DOMParser = function() {
|
50
|
const parser = new orig_DOMParser();
|
51
|
this.parseFromString = () => parser.parseFromString('', 'text/html');
|
52
|
}
|
53
|
}
|
54
|
|
55
|
enforce_blocking(what_to_do.policy);
|
56
|
}'''
|
57
|
|
58
|
def get(driver, page, what_to_do):
|
59
|
driver.get(page + '#' + urllib.parse.quote(json.dumps(what_to_do)))
|
60
|
driver.execute_script('window.before_reload = true; location.reload();')
|
61
|
done = lambda _: not driver.execute_script('return window.before_reload;')
|
62
|
WebDriverWait(driver, 10).until(done)
|
63
|
|
64
|
@pytest.mark.ext_data({'content_script': content_script})
|
65
|
@pytest.mark.usefixtures('webextension')
|
66
|
# Under Mozilla we use several mechanisms of script blocking. Some serve as
|
67
|
# fallbacks in case others break. CSP one of those mechanisms. Here we run the
|
68
|
# test once with CSP blocking on and once without it. This allows us to verify
|
69
|
# that the CSP-less blocking approaches by themselves also work. We don't do the
|
70
|
# reverse (CSP on and other mechanisms off) because CSP rules added through
|
71
|
# <meta> injection are not reliable enough - they do not always take effect
|
72
|
# immediately and there's nothing we can do to fix it.
|
73
|
@pytest.mark.parametrize('csp_off_setting', [{}, {'csp_off': True}])
|
74
|
def test_policy_enforcing_html(driver, execute_in_page, csp_off_setting):
|
75
|
"""
|
76
|
A test case of sanitizing <script>s and intrinsic javascript in pages.
|
77
|
"""
|
78
|
def assert_properly_blocked():
|
79
|
for i in range(1, 3):
|
80
|
driver.find_element_by_id(f'clickme{i}').click()
|
81
|
|
82
|
assert set(driver.execute_script('return window.__run || [];')) == set()
|
83
|
assert bool(csp_off_setting) == are_scripts_allowed(driver)
|
84
|
|
85
|
for attr in ('onclick', 'href', 'src', 'data'):
|
86
|
elem = driver.find_element_by_css_selector(f'[blocked-{attr}]')
|
87
|
|
88
|
assert 'blocked' in elem.get_attribute(attr)
|
89
|
assert '__run = [...(' in elem.get_attribute(f'blocked-{attr}')
|
90
|
|
91
|
but1 = driver.find_element_by_id('clickme1')
|
92
|
assert but1.get_attribute('blocked-blocked-onclick') == \
|
93
|
"some useful data"
|
94
|
|
95
|
# First, see if scripts run when not blocked.
|
96
|
get(driver, 'https://gotmyowndoma.in/scripts_to_block_1.html', {
|
97
|
'policy': allow_policy,
|
98
|
**csp_off_setting
|
99
|
})
|
100
|
|
101
|
for i in range(1, 3):
|
102
|
driver.find_element_by_id(f'clickme{i}').click()
|
103
|
|
104
|
assert set(driver.execute_script('return window.__run || [];')) == \
|
105
|
{'inline', 'on', 'href', 'src', 'data'}
|
106
|
assert are_scripts_allowed(driver)
|
107
|
|
108
|
# Now, verify scripts don't run when blocked.
|
109
|
get(driver, 'https://gotmyowndoma.in/scripts_to_block_1.html', {
|
110
|
'policy': block_policy,
|
111
|
**csp_off_setting
|
112
|
})
|
113
|
|
114
|
assert_properly_blocked()
|
115
|
|
116
|
# Now, verify only scripts with nonce can run when payload is injected.
|
117
|
get(driver, 'https://gotmyowndoma.in/scripts_to_block_1.html', {
|
118
|
'policy': payload_policy,
|
119
|
**csp_off_setting
|
120
|
})
|
121
|
|
122
|
assert_properly_blocked()
|
123
|
assert are_scripts_allowed(driver, nonce)
|