Headline
CVE-2022-0877: Updated CSP with frame-src rules · BookStackApp/BookStack@856fca8
Cross-site Scripting (XSS) - Stored in GitHub repository bookstackapp/bookstack prior to v22.02.3.
@@ -3,12 +3,10 @@ namespace BookStack\Util;
use Illuminate\Support\Str; use Symfony\Component\HttpFoundation\Response;
class CspService { /** @var string */ protected $nonce; protected string $nonce;
public function __construct(string $nonce = ‘’) { @@ -24,13 +22,51 @@ public function getNonce(): string }
/** * Sets CSP ‘script-src’ headers to restrict the forms of script that can * run on the page. * Get the CSP headers for the application */ public function setScriptSrc(Response $response) public function getCspHeader(): string { $headers = [ $this->getFrameAncestors(), $this->getFrameSrc(), $this->getScriptSrc(), $this->getObjectSrc(), $this->getBaseUri(), ];
return implode('; ', array_filter($headers)); }
/** * Get the CSP rules for the application for a HTML meta tag. */ public function getCspMetaTagValue(): string { $headers = [ $this->getFrameSrc(), $this->getScriptSrc(), $this->getObjectSrc(), $this->getBaseUri(), ];
return implode('; ', array_filter($headers)); }
/** * Check if the user has configured some allowed iframe hosts. */ public function allowedIFrameHostsConfigured(): bool { return count($this->getAllowedIframeHosts()) > 0; }
/** * Create CSP ‘script-src’ rule to restrict the forms of script that can run on the page. */ protected function getScriptSrc(): string { if (config(‘app.allow_content_scripts’)) { return; return '’; }
$parts = [ @@ -40,51 +76,50 @@ public function setScriptSrc(Response $response) '\’strict-dynamic\’’, ];
$value = 'script-src ' . implode(' ', $parts); $response->headers->set('Content-Security-Policy’, $value, false); return 'script-src ' . implode(' ', $parts); }
/** * Sets CSP “frame-ancestors” headers to restrict the hosts that BookStack can be * iframed within. Also adjusts the cookie samesite options so that cookies will * operate in the third-party context. * Create CSP “frame-ancestors” rule to restrict the hosts that BookStack can be iframed within. */ public function setFrameAncestors(Response $response) protected function getFrameAncestors(): string { $iframeHosts = $this->getAllowedIframeHosts(); array_unshift($iframeHosts, “’self’”); $cspValue = 'frame-ancestors ' . implode(' ', $iframeHosts); $response->headers->set('Content-Security-Policy’, $cspValue, false); return 'frame-ancestors ' . implode(' ', $iframeHosts); }
/** * Check if the user has configured some allowed iframe hosts. * Creates CSP “frame-src” rule to restrict what hosts/sources can be loaded * within iframes to provide an allow-list-style approach to iframe content. */ public function allowedIFrameHostsConfigured(): bool protected function getFrameSrc(): string { return count($this->getAllowedIframeHosts()) > 0; $iframeHosts = $this->getAllowedIframeSources(); array_unshift($iframeHosts, “’self’”); return 'frame-src ' . implode(' ', $iframeHosts); }
/** * Sets CSP ‘object-src’ headers to restrict the types of dynamic content * Creates CSP ‘object-src’ rule to restrict the types of dynamic content * that can be embedded on the page. */ public function setObjectSrc(Response $response) protected function getObjectSrc(): string { if (config(‘app.allow_content_scripts’)) { return; return '’; }
$response->headers->set('Content-Security-Policy’, 'object-src \’self\’’, false); return "object-src 'self’"; }
/** * Sets CSP ‘base-uri’ headers to restrict what base tags can be set on * Creates CSP ‘base-uri’ rule to restrict what base tags can be set on * the page to prevent manipulation of relative links. */ public function setBaseUri(Response $response) protected function getBaseUri(): string { $response->headers->set('Content-Security-Policy’, 'base-uri \’self\’’, false); return "base-uri 'self’"; }
protected function getAllowedIframeHosts(): array @@ -93,4 +128,21 @@ protected function getAllowedIframeHosts(): array
return array_filter(explode(' ', $hosts)); }
protected function getAllowedIframeSources(): array { $sources = config('app.iframe_sources’, ‘’); $hosts = array_filter(explode(' ', $sources));
// Extract drawing service url to allow embedding if active $drawioConfigValue = config(‘services.drawio’); if ($drawioConfigValue) { $drawioSource = is_string($drawioConfigValue) ? $drawioConfigValue : 'https://embed.diagrams.net/’; $drawioSourceParsed = parse_url($drawioSource); $drawioHost = $drawioSourceParsed[‘scheme’] . ‘://’ . $drawioSourceParsed[‘host’]; $hosts[] = $drawioHost; }
return $hosts; } }