diff options
author | Ferdinand Thiessen <opensource@fthiessen.de> | 2023-11-17 10:56:02 +0100 |
---|---|---|
committer | Ferdinand Thiessen <opensource@fthiessen.de> | 2023-11-17 11:12:57 +0100 |
commit | 7df9eb335199b3eb3016f92e8cba47e12366f306 (patch) | |
tree | 93d30d426a5fa2e10abf89e359fccccd1c87370e /lib | |
parent | 165178a6ad8338145a8bf4432bc19e80b74c0696 (diff) | |
download | nextcloud-server-7df9eb335199b3eb3016f92e8cba47e12366f306.tar.gz nextcloud-server-7df9eb335199b3eb3016f92e8cba47e12366f306.zip |
feat(ContentSecurityPolicy): Allow to set `strict-dynamic` on `script-src-elem` only
This adds the possibility to set `strict-dynamic` on `script-src-elem` only while keep the default rules for `script-src`.
The idea is to allow loading module js which imports other files and thus does not allow nonces on import but on the initial script tag.
Signed-off-by: Ferdinand Thiessen <opensource@fthiessen.de>
Diffstat (limited to 'lib')
-rw-r--r-- | lib/private/Security/CSP/ContentSecurityPolicy.php | 8 | ||||
-rw-r--r-- | lib/public/AppFramework/Http/ContentSecurityPolicy.php | 2 | ||||
-rw-r--r-- | lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php | 34 |
3 files changed, 38 insertions, 6 deletions
diff --git a/lib/private/Security/CSP/ContentSecurityPolicy.php b/lib/private/Security/CSP/ContentSecurityPolicy.php index eca3e2b6b29..ee525af4c2a 100644 --- a/lib/private/Security/CSP/ContentSecurityPolicy.php +++ b/lib/private/Security/CSP/ContentSecurityPolicy.php @@ -191,4 +191,12 @@ class ContentSecurityPolicy extends \OCP\AppFramework\Http\ContentSecurityPolicy public function setStrictDynamicAllowed(bool $strictDynamicAllowed): void { $this->strictDynamicAllowed = $strictDynamicAllowed; } + + public function isStrictDynamicAllowedOnScripts(): bool { + return $this->strictDynamicAllowedOnScripts; + } + + public function setStrictDynamicAllowedOnScripts(bool $strictDynamicAllowedOnScripts): void { + $this->strictDynamicAllowedOnScripts = $strictDynamicAllowedOnScripts; + } } diff --git a/lib/public/AppFramework/Http/ContentSecurityPolicy.php b/lib/public/AppFramework/Http/ContentSecurityPolicy.php index f17dd9bd270..386d908ffb6 100644 --- a/lib/public/AppFramework/Http/ContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/ContentSecurityPolicy.php @@ -48,6 +48,8 @@ class ContentSecurityPolicy extends EmptyContentSecurityPolicy { protected ?bool $evalWasmAllowed = false; /** @var bool Whether strict-dynamic should be set */ protected $strictDynamicAllowed = false; + /** @var bool Whether strict-dynamic should be set for 'script-src-elem' */ + protected $strictDynamicAllowedOnScripts = false; /** @var array Domains from which scripts can get loaded */ protected $allowedScriptDomains = [ '\'self\'', diff --git a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php index 7e1de2ef2eb..960efa75d2c 100644 --- a/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php +++ b/lib/public/AppFramework/Http/EmptyContentSecurityPolicy.php @@ -41,6 +41,8 @@ class EmptyContentSecurityPolicy { protected $useJsNonce = null; /** @var bool Whether strict-dynamic should be used */ protected $strictDynamicAllowed = null; + /** @var bool Whether strict-dynamic should be used on script-src-elem */ + protected $strictDynamicAllowedOnScripts = null; /** * @var bool Whether eval in JS scripts is allowed * TODO: Disallow per default @@ -94,6 +96,18 @@ class EmptyContentSecurityPolicy { } /** + * In contrast to `useStrictDynamic` this only sets strict-dynamic on script-src-elem + * Meaning only grants trust to all imports of scripts that were loaded in `<script>` tags, and thus weakens less the CSP. + * @param bool $state + * @return EmptyContentSecurityPolicy + * @since 28.0.0 + */ + public function useStrictDynamicOnScripts(bool $state = false): self { + $this->strictDynamicAllowedOnScripts = $state; + return $this; + } + + /** * Use the according JS nonce * This method is only for CSPMiddleware, custom values are ignored in mergePolicies of ContentSecurityPolicyManager * @@ -448,27 +462,35 @@ class EmptyContentSecurityPolicy { if (!empty($this->allowedScriptDomains) || $this->evalScriptAllowed || $this->evalWasmAllowed) { $policy .= 'script-src '; + $scriptSrc = ''; if (is_string($this->useJsNonce)) { if ($this->strictDynamicAllowed) { - $policy .= '\'strict-dynamic\' '; + $scriptSrc .= '\'strict-dynamic\' '; } - $policy .= '\'nonce-'.base64_encode($this->useJsNonce).'\''; + $scriptSrc .= '\'nonce-'.base64_encode($this->useJsNonce).'\''; $allowedScriptDomains = array_flip($this->allowedScriptDomains); unset($allowedScriptDomains['\'self\'']); $this->allowedScriptDomains = array_flip($allowedScriptDomains); if (count($allowedScriptDomains) !== 0) { - $policy .= ' '; + $scriptSrc .= ' '; } } if (is_array($this->allowedScriptDomains)) { - $policy .= implode(' ', $this->allowedScriptDomains); + $scriptSrc .= implode(' ', $this->allowedScriptDomains); } if ($this->evalScriptAllowed) { - $policy .= ' \'unsafe-eval\''; + $scriptSrc .= ' \'unsafe-eval\''; } if ($this->evalWasmAllowed) { - $policy .= ' \'wasm-unsafe-eval\''; + $scriptSrc .= ' \'wasm-unsafe-eval\''; } + $policy .= $scriptSrc . ';'; + } + + // We only need to set this if 'strictDynamicAllowed' is not set because otherwise we can simply fall back to script-src + if ($this->strictDynamicAllowedOnScripts && !(is_string($this->useJsNonce) && $this->strictDynamicAllowed)) { + $policy .= 'script-src-elem \'strict-dynamic\' '; + $policy .= $scriptSrc ?? ''; $policy .= ';'; } |