* * For the full copyright and license information, please view * the LICENSE file that was distributed with this source code. */ namespace CodeIgniter\HTTP; use Config\App; use Config\ContentSecurityPolicy as ContentSecurityPolicyConfig; /** * Provides tools for working with the Content-Security-Policy header * to help defeat XSS attacks. * * @see http://www.w3.org/TR/CSP/ * @see http://www.html5rocks.com/en/tutorials/security/content-security-policy/ * @see http://content-security-policy.com/ * @see https://www.owasp.org/index.php/Content_Security_Policy * @see \CodeIgniter\HTTP\ContentSecurityPolicyTest */ class ContentSecurityPolicy { /** * Used for security enforcement * * @var array|string */ protected $baseURI = []; /** * Used for security enforcement * * @var array|string */ protected $childSrc = []; /** * Used for security enforcement * * @var array */ protected $connectSrc = []; /** * Used for security enforcement * * @var array|string */ protected $defaultSrc = []; /** * Used for security enforcement * * @var array|string */ protected $fontSrc = []; /** * Used for security enforcement * * @var array|string */ protected $formAction = []; /** * Used for security enforcement * * @var array|string */ protected $frameAncestors = []; /** * Used for security enforcement * * @var array|string */ protected $frameSrc = []; /** * Used for security enforcement * * @var array|string */ protected $imageSrc = []; /** * Used for security enforcement * * @var array|string */ protected $mediaSrc = []; /** * Used for security enforcement * * @var array|string */ protected $objectSrc = []; /** * Used for security enforcement * * @var array|string */ protected $pluginTypes = []; /** * Used for security enforcement * * @var string */ protected $reportURI; /** * Used for security enforcement * * @var array|string */ protected $sandbox = []; /** * Used for security enforcement * * @var array|string */ protected $scriptSrc = []; /** * Used for security enforcement * * @var array|string */ protected $styleSrc = []; /** * Used for security enforcement * * @var array|string */ protected $manifestSrc = []; /** * Used for security enforcement * * @var bool */ protected $upgradeInsecureRequests = false; /** * Used for security enforcement * * @var bool */ protected $reportOnly = false; /** * Used for security enforcement * * @var array */ protected $validSources = [ 'self', 'none', 'unsafe-inline', 'unsafe-eval', ]; /** * Used for security enforcement * * @var array */ protected $nonces = []; /** * Nonce for style * * @var string */ protected $styleNonce; /** * Nonce for script * * @var string */ protected $scriptNonce; /** * Nonce tag for style * * @var string */ protected $styleNonceTag = '{csp-style-nonce}'; /** * Nonce tag for script * * @var string */ protected $scriptNonceTag = '{csp-script-nonce}'; /** * Replace nonce tag automatically * * @var bool */ protected $autoNonce = true; /** * An array of header info since we have * to build ourself before passing to Response. * * @var array */ protected $tempHeaders = []; /** * An array of header info to build * that should only be reported. * * @var array */ protected $reportOnlyHeaders = []; /** * Whether Content Security Policy is being enforced. * * @var bool */ protected $CSPEnabled = false; /** * Constructor. * * Stores our default values from the Config file. */ public function __construct(ContentSecurityPolicyConfig $config) { $appConfig = config(App::class); $this->CSPEnabled = $appConfig->CSPEnabled; foreach (get_object_vars($config) as $setting => $value) { if (property_exists($this, $setting)) { $this->{$setting} = $value; } } if (! is_array($this->styleSrc)) { $this->styleSrc = [$this->styleSrc]; } if (! is_array($this->scriptSrc)) { $this->scriptSrc = [$this->scriptSrc]; } } /** * Whether Content Security Policy is being enforced. */ public function enabled(): bool { return $this->CSPEnabled; } /** * Get the nonce for the style tag. */ public function getStyleNonce(): string { if ($this->styleNonce === null) { $this->styleNonce = bin2hex(random_bytes(12)); $this->styleSrc[] = 'nonce-' . $this->styleNonce; } return $this->styleNonce; } /** * Get the nonce for the script tag. */ public function getScriptNonce(): string { if ($this->scriptNonce === null) { $this->scriptNonce = bin2hex(random_bytes(12)); $this->scriptSrc[] = 'nonce-' . $this->scriptNonce; } return $this->scriptNonce; } /** * Compiles and sets the appropriate headers in the request. * * Should be called just prior to sending the response to the user agent. * * @return void */ public function finalize(ResponseInterface $response) { if ($this->autoNonce) { $this->generateNonces($response); } $this->buildHeaders($response); } /** * If TRUE, nothing will be restricted. Instead all violations will * be reported to the reportURI for monitoring. This is useful when * you are just starting to implement the policy, and will help * determine what errors need to be addressed before you turn on * all filtering. * * @return $this */ public function reportOnly(bool $value = true) { $this->reportOnly = $value; return $this; } /** * Adds a new base_uri value. Can be either a URI class or a simple string. * * base_uri restricts the URLs that can appear in a page's element. * * @see http://www.w3.org/TR/CSP/#directive-base-uri * * @param array|string $uri * * @return $this */ public function addBaseURI($uri, ?bool $explicitReporting = null) { $this->addOption($uri, 'baseURI', $explicitReporting ?? $this->reportOnly); return $this; } /** * Adds a new valid endpoint for a form's action. Can be either * a URI class or a simple string. * * child-src lists the URLs for workers and embedded frame contents. * For example: child-src https://youtube.com would enable embedding * videos from YouTube but not from other origins. * * @see http://www.w3.org/TR/CSP/#directive-child-src * * @param array|string $uri * * @return $this */ public function addChildSrc($uri, ?bool $explicitReporting = null) { $this->addOption($uri, 'childSrc', $explicitReporting ?? $this->reportOnly); return $this; } /** * Adds a new valid endpoint for a form's action. Can be either * a URI class or a simple string. * * connect-src limits the origins to which you can connect * (via XHR, WebSockets, and EventSource). * * @see http://www.w3.org/TR/CSP/#directive-connect-src * * @param array|string $uri * * @return $this */ public function addConnectSrc($uri, ?bool $explicitReporting = null) { $this->addOption($uri, 'connectSrc', $explicitReporting ?? $this->reportOnly); return $this; } /** * Adds a new valid endpoint for a form's action. Can be either * a URI class or a simple string. * * default_src is the URI that is used for many of the settings when * no other source has been set. * * @see http://www.w3.org/TR/CSP/#directive-default-src * * @param array|string $uri * * @return $this */ public function setDefaultSrc($uri, ?bool $explicitReporting = null) { $this->defaultSrc = [(string) $uri => $explicitReporting ?? $this->reportOnly]; return $this; } /** * Adds a new valid endpoint for a form's action. Can be either * a URI class or a simple string. * * font-src specifies the origins that can serve web fonts. * * @see http://www.w3.org/TR/CSP/#directive-font-src * * @param array|string $uri * * @return $this */ public function addFontSrc($uri, ?bool $explicitReporting = null) { $this->addOption($uri, 'fontSrc', $explicitReporting ?? $this->reportOnly); return $this; } /** * Adds a new valid endpoint for a form's action. Can be either * a URI class or a simple string. * * @see http://www.w3.org/TR/CSP/#directive-form-action * * @param array|string $uri * * @return $this */ public function addFormAction($uri, ?bool $explicitReporting = null) { $this->addOption($uri, 'formAction', $explicitReporting ?? $this->reportOnly); return $this; } /** * Adds a new resource that should allow embedding the resource using * ,