diff options
author | Wojtek Kosior <koszko@koszko.org> | 2022-01-17 11:20:52 +0100 |
---|---|---|
committer | Wojtek Kosior <koszko@koszko.org> | 2022-01-17 11:24:56 +0100 |
commit | 7bedbcbd80eba9359d2e905b7693923c76ce563d (patch) | |
tree | 5059ac406e29b1b1e81639fc11316dde280fe218 /test | |
parent | ede3a55ba22d2560ec7c0deebffd73623488acc1 (diff) | |
download | browser-extension-7bedbcbd80eba9359d2e905b7693923c76ce563d.tar.gz browser-extension-7bedbcbd80eba9359d2e905b7693923c76ce563d.zip |
move policy enforcing code to a new file, include basic test
Diffstat (limited to 'test')
-rw-r--r-- | test/data/pages/gotmyowndomain.html | 2 | ||||
-rw-r--r-- | test/data/pages/gotmyowndomain_https.html | 4 | ||||
-rw-r--r-- | test/data/pages/scripts_to_block_1.html | 44 | ||||
-rw-r--r-- | test/unit/test_policy_enforcing.py | 110 | ||||
-rw-r--r-- | test/unit/test_webrequest.py | 14 | ||||
-rw-r--r-- | test/unit/utils.py | 13 | ||||
-rw-r--r-- | test/world_wide_library.py | 3 |
7 files changed, 174 insertions, 16 deletions
diff --git a/test/data/pages/gotmyowndomain.html b/test/data/pages/gotmyowndomain.html index 42c26cc..390cbcc 100644 --- a/test/data/pages/gotmyowndomain.html +++ b/test/data/pages/gotmyowndomain.html @@ -2,7 +2,7 @@ <!-- SPDX-License-Identifier: AGPL-3.0-or-later - Sample testig page + Sample testing page This file is part of Haketilo. diff --git a/test/data/pages/gotmyowndomain_https.html b/test/data/pages/gotmyowndomain_https.html index 95c0be4..f602950 100644 --- a/test/data/pages/gotmyowndomain_https.html +++ b/test/data/pages/gotmyowndomain_https.html @@ -2,7 +2,7 @@ <!-- SPDX-License-Identifier: AGPL-3.0-or-later - Sample testig page to serve over HTTPS + Sample testing page to serve over HTTPS This file is part of Haketilo. @@ -23,7 +23,7 @@ --> <html> <head> - <meta name=charset value="latin1"> + <meta name="charset" value="latin1"> <title>Schrodinger's Document</title> </head> <body> diff --git a/test/data/pages/scripts_to_block_1.html b/test/data/pages/scripts_to_block_1.html new file mode 100644 index 0000000..6d868dd --- /dev/null +++ b/test/data/pages/scripts_to_block_1.html @@ -0,0 +1,44 @@ +<!DOCTYPE html> +<!-- + SPDX-License-Identifier: CC0-1.0 + + A testing page with various scripts that need to get blocked. + + This file is part of Haketilo. + + Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> + + This program is free software: you can redistribute it and/or modify + it under the terms of the CC0 1.0 Universal License as published by + the Creative Commons Corporation. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + CC0 1.0 Universal License for more details. + --> +<html> + <head> + <script> + window.__run = [...(window.__run || []), 'inline']; + </script> + <!-- the one below shall not execute even when blocking is off... --> + <script type="application/json"> + window.__run = [...(window.__run || []), 'json']; + </script> + </head> + <body> + <button id="clickme1" + onclick="window.__run = [...(window.__run || []), 'on'];"> + Click Meee! + </button> + <a id="clickme2" + href="javascript:window.__run = [...(window.__run || []), 'href'];void(0);"> + Click Meee! + </a> + <iframe src="javascript:window.parent.__run = [...(window.parent.__run || []), 'src'];"> + </iframe> + <object data="javascript:window.__run = [...(window.__run || []), 'data'];"> + </object> + </body> +</html> diff --git a/test/unit/test_policy_enforcing.py b/test/unit/test_policy_enforcing.py new file mode 100644 index 0000000..2f7bc80 --- /dev/null +++ b/test/unit/test_policy_enforcing.py @@ -0,0 +1,110 @@ +# SPDX-License-Identifier: CC0-1.0 + +""" +Haketilo unit tests - enforcing script blocking policy from content script +""" + +# This file is part of Haketilo +# +# Copyright (C) 2022 Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the CC0 1.0 Universal License as published by +# the Creative Commons Corporation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# CC0 1.0 Universal License for more details. + +import pytest +import json +import urllib.parse +from selenium.webdriver.support.ui import WebDriverWait + +from ..script_loader import load_script +from .utils import are_scripts_allowed + +# For simplicity, we'll use one nonce in all test cases. +nonce = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + +allow_policy = {'allow': True} +block_policy = { + 'allow': False, + 'csp': f"prefetch-src 'none'; script-src-attr 'none'; script-src 'none'; script-src-elem 'none'; frame-src http://* https://*;" +} +payload_policy = { + 'mapping': 'somemapping', + 'payload': {'identifier': 'someresource'}, + 'csp': f"prefetch-src 'none'; script-src-attr 'none'; script-src 'nonce-{nonce}'; script-src-elem 'nonce-{nonce}';" +} + +content_script = load_script('content/policy_enforcing.js') + ''';{ +const smuggled_what_to_do = /^[^#]*#?(.*)$/.exec(document.URL)[1]; +const what_to_do = smuggled_what_to_do === "" ? {allow: true} : + JSON.parse(decodeURIComponent(smuggled_what_to_do)); + +if (what_to_do.csp_off) { + const orig_DOMParser = window.DOMParser; + window.DOMParser = function() { + parser = new orig_DOMParser(); + this.parseFromString = () => parser.parseFromString('', 'text/html'); + } +} + +if (what_to_do.onbeforescriptexecute_off) + prevent_script_execution = () => {}; + +if (what_to_do.sanitize_script_off) { + sanitize_script = () => {}; + desanitize_script = () => {}; +} + +enforce_blocking(what_to_do.policy); +}''' + +def get(driver, page, what_to_do): + driver.get(page + '#' + urllib.parse.quote(json.dumps(what_to_do))) + driver.execute_script('window.before_reload = true; location.reload();') + done = lambda _: not driver.execute_script('return window.before_reload;') + WebDriverWait(driver, 10).until(done) + +@pytest.mark.ext_data({'content_script': content_script}) +@pytest.mark.usefixtures('webextension') +def test_policy_enforcing(driver, execute_in_page): + """ + A test case of sanitizing <script>s and <meta>s in pages. + """ + # First, see if scripts run when not blocked. + get(driver, 'https://gotmyowndoma.in/scripts_to_block_1.html', { + 'policy': allow_policy + }) + + for i in range(1, 3): + driver.find_element_by_id(f'clickme{i}').click() + + assert set(driver.execute_script('return window.__run || [];')) == \ + {'inline', 'on', 'href', 'src', 'data'} + + # Now, verify scripts don't run when blocked. + get(driver, 'https://gotmyowndoma.in/scripts_to_block_1.html', { + 'policy': block_policy + }) + + for i in range(1, 3): + driver.find_element_by_id(f'clickme{i}').click() + + assert set(driver.execute_script('return window.__run || [];')) == set() + assert not are_scripts_allowed(driver) + + # Now, verify only scripts with nonce can run when payload is injected. + get(driver, 'https://gotmyowndoma.in/scripts_to_block_1.html', { + 'policy': payload_policy + }) + + for i in range(1, 3): + driver.find_element_by_id(f'clickme{i}').click() + + assert set(driver.execute_script('return window.__run || [];')) == set() + assert not are_scripts_allowed(driver) + assert are_scripts_allowed(driver, nonce) diff --git a/test/unit/test_webrequest.py b/test/unit/test_webrequest.py index ae617aa..598f43b 100644 --- a/test/unit/test_webrequest.py +++ b/test/unit/test_webrequest.py @@ -22,6 +22,7 @@ from hashlib import sha256 import pytest from ..script_loader import load_script +from .utils import are_scripts_allowed def webrequest_js(): return (load_script('background/webrequest.js', @@ -50,19 +51,6 @@ def webrequest_js(): start("somesecret"); ''') -def are_scripts_allowed(driver, nonce=None): - return driver.execute_script( - ''' - document.scripts_allowed = false; - const script = document.createElement("script"); - script.innerHTML = "document.scripts_allowed = true;"; - if (arguments[0]) - script.setAttribute("nonce", arguments[0]); - document.head.append(script); - return document.scripts_allowed; - ''', - nonce) - @pytest.mark.ext_data({'background_script': webrequest_js}) @pytest.mark.usefixtures('webextension') def test_on_headers_received(driver, execute_in_page): diff --git a/test/unit/utils.py b/test/unit/utils.py index 96ebf60..8e04d91 100644 --- a/test/unit/utils.py +++ b/test/unit/utils.py @@ -187,3 +187,16 @@ def is_prime(n): return n > 1 and all([n % i != 0 for i in range(2, n)]) broker_js = lambda: load_script('background/broadcast_broker.js') + ';start();' + +def are_scripts_allowed(driver, nonce=None): + return driver.execute_script( + ''' + document.scripts_allowed = false; + const script = document.createElement("script"); + script.innerHTML = "document.scripts_allowed = true;"; + if (arguments[0]) + script.setAttribute("nonce", arguments[0]); + document.head.append(script); + return document.scripts_allowed; + ''', + nonce) diff --git a/test/world_wide_library.py b/test/world_wide_library.py index 4865b0a..f66a6d5 100644 --- a/test/world_wide_library.py +++ b/test/world_wide_library.py @@ -100,6 +100,9 @@ catalog = { 'https://gotmyowndoma.in/index.html': (200, {}, here / 'data' / 'pages' / 'gotmyowndomain_https.html'), + 'https://gotmyowndoma.in/scripts_to_block_1.html': + (200, {}, here / 'data' / 'pages' / 'scripts_to_block_1.html'), + 'https://serve.scrip.ts/': serve_script, 'https://site.with.scripts.block.ed': |