From 801f3758d20111bc7ec1c03272dd70de8411cf60 Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Mon, 16 Sep 2024 14:53:19 +0200 Subject: [PATCH 01/15] Send a configurable CSP in every HTML response The CSP gets adapted to remote objects being allowed or not. It can be configured or disabled via the config option `content_security_policy` (and `content_security_policy_add_allow_remote`). --- config/defaults.inc.php | 11 +++++++++++ program/include/rcmail_output_html.php | 18 ++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/config/defaults.inc.php b/config/defaults.inc.php index 35901ab698e..bb61f16cc8b 100644 --- a/config/defaults.inc.php +++ b/config/defaults.inc.php @@ -1563,3 +1563,14 @@ // 0 - Reply-All always // 1 - Reply-List if mailing list is detected $config['reply_all_mode'] = 0; + +// The Content-Security-Policy to use if no remote objects are allowed to +// be loaded. If you use plugins you might need to extend this. +// Only change this if you know what you're doing! You can break the whole +// application with changes to this setting! +// To disable completely set the value to `false`; +$config['content_security_policy'] = "default-src 'self' data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"; + +// Additions to the Content-Security-Policy to use if remote objects *are* +// allowed to be loaded. +$config['content_security_policy_add_allow_remote'] = 'img-src *; media-src *; font-src: *; frame-src: *;'; diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 75e9fbf0e17..07a8cb5e38b 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -728,6 +728,8 @@ public function page_headers() $this->header('X-Frame-Options: sameorigin', true); } } + + $this->add_csp_header(); } /** @@ -2717,4 +2719,20 @@ protected function get_template_logo($type = null, $match = null) return $template_logo; } + + /** + * Add the Content-Security-Policy to the HTTP response headers (unless it + * is disabled). + */ + protected function add_csp_header(): void + { + $csp = $this->app->config->get('content_security_policy'); + if (!in_array($csp, ['', false, 'false'])) { + $csp_header = "Content-Security-Policy: {$csp}"; + if (isset($this->env['safemode']) && $this->env['safemode'] === true) { + $csp_header .= $this->app->config->get('content_security_policy_add_allow_remote'); + } + $this->header($csp_header); + } + } } From 0d1b036f7aabdc64a9cbc73ac3bb1052bcd0108c Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Mon, 28 Oct 2024 16:19:37 +0100 Subject: [PATCH 02/15] Ensure proper semicolons between CSP-parts. --- program/include/rcmail_output_html.php | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 07a8cb5e38b..7d7522e03a2 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -2726,13 +2726,26 @@ protected function get_template_logo($type = null, $match = null) */ protected function add_csp_header(): void { - $csp = $this->app->config->get('content_security_policy'); + $csp = $this->get_csp_value('content_security_policy'); if (!in_array($csp, ['', false, 'false'])) { $csp_header = "Content-Security-Policy: {$csp}"; if (isset($this->env['safemode']) && $this->env['safemode'] === true) { - $csp_header .= $this->app->config->get('content_security_policy_add_allow_remote'); + $csp_allow_remote = $this->get_csp_value('content_security_policy_add_allow_remote'); + $csp_header .= "; {$csp_allow_remote}"; } $this->header($csp_header); } } + + /** + * Get a CSP-related value from the config, stripped by surrounding + * whitespace and semicolons (and NUL byte, because it's included in the + * default second argument to trim(), too). + * + * @param $name string The key of the wanted config value + */ + protected function get_csp_value($name): string + { + return trim($this->app->config->get($name), "; \n\r\t\v\x00"); + } } From 3882b73408551b0aa95f9f287203b7a9c99be16a Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Mon, 28 Oct 2024 17:47:21 +0100 Subject: [PATCH 03/15] Also use default value in case of `null` --- program/include/rcmail_output_html.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/program/include/rcmail_output_html.php b/program/include/rcmail_output_html.php index 7d7522e03a2..6dc08225931 100644 --- a/program/include/rcmail_output_html.php +++ b/program/include/rcmail_output_html.php @@ -2727,7 +2727,7 @@ protected function get_template_logo($type = null, $match = null) protected function add_csp_header(): void { $csp = $this->get_csp_value('content_security_policy'); - if (!in_array($csp, ['', false, 'false'])) { + if (!in_array($csp, ['', false, 'false', null])) { $csp_header = "Content-Security-Policy: {$csp}"; if (isset($this->env['safemode']) && $this->env['safemode'] === true) { $csp_allow_remote = $this->get_csp_value('content_security_policy_add_allow_remote'); From 6f00ac27eea7233db9c878f6233c2b8bf70fd8c4 Mon Sep 17 00:00:00 2001 From: Pablo Zmdl Date: Tue, 29 Oct 2024 17:38:37 +0100 Subject: [PATCH 04/15] Add unit-tests for adding CSP header --- tests/Rcmail/OutputHtmlTest.php | 68 +++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/tests/Rcmail/OutputHtmlTest.php b/tests/Rcmail/OutputHtmlTest.php index 45c359b288b..159f045b89f 100644 --- a/tests/Rcmail/OutputHtmlTest.php +++ b/tests/Rcmail/OutputHtmlTest.php @@ -429,4 +429,72 @@ public function test_charset_selector() $this->assertTrue(strpos($result, '