diff --git a/.docker/frankenphp/Caddyfile b/.docker/frankenphp/Caddyfile index f26b6f22..293ab18e 100644 --- a/.docker/frankenphp/Caddyfile +++ b/.docker/frankenphp/Caddyfile @@ -51,6 +51,15 @@ # Disable Topics tracking if not enabled explicitly: https://github.com/jkarlin/topics header ?Permissions-Policy "browsing-topics=()" + # Set a strict CSP and nosniff for all static assets not handled by PHP. + # ? means "set only if not already present", so PHP responses carrying a Nelmio CSP are left untouched. + header ?Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;" + header ?X-Content-Type-Options "nosniff" + + # SVG files get a slightly different CSP because they can embed resources and must not be framed. + @svg path *.svg *.svg.gz *.svg.br + header @svg Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;" + # Prevent PHP execution in the media upload directory @php_in_media path_regexp (?i)^/media/.*\.(php[3-8]?|phar|phtml|pht|phps)$ respond @php_in_media 403 diff --git a/Dockerfile b/Dockerfile index e848acc1..049de283 100644 --- a/Dockerfile +++ b/Dockerfile @@ -193,7 +193,7 @@ RUN a2dissite 000-default.conf && \ a2enmod proxy_fcgi setenvif && \ a2enconf php${PHP_VERSION}-fpm && \ a2enconf docker-php && \ - a2enmod rewrite + a2enmod rewrite headers # Install composer and yarn dependencies for Part-DB USER www-data diff --git a/docs/installation/installation_guide-debian.md b/docs/installation/installation_guide-debian.md index e9f500b8..2915adbb 100644 --- a/docs/installation/installation_guide-debian.md +++ b/docs/installation/installation_guide-debian.md @@ -232,7 +232,7 @@ sudo ln -s /etc/apache2/sites-available/partdb.conf /etc/apache2/sites-enabled/p Configure apache to show pretty URL paths for Part-DB (`/label/dialog` instead of `/index.php/label/dialog`): ```bash -sudo a2enmod rewrite +sudo a2enmod rewrite headers ``` If you want to access Part-DB via the IP-Address of the server, instead of the domain name, you have to remove the diff --git a/docs/installation/nginx.md b/docs/installation/nginx.md index 981c18d5..1ae1d32c 100644 --- a/docs/installation/nginx.md +++ b/docs/installation/nginx.md @@ -36,6 +36,10 @@ server { root /var/www/partdb/public; location / { + # Headers are set here for static assets. PHP responses are served via the index.php location + # below and inherit neither of these headers, so Nelmio's PHP-side CSP is unaffected. + add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;" always; + add_header X-Content-Type-Options "nosniff" always; try_files $uri /index.php$is_args$args; } @@ -57,10 +61,12 @@ server { location ~* ^/media/.*\.(php[3-8]?|phar|phtml|pht|phps)$ { return 403; } - - # Set Content-Security-Policy for svg files, to block embedded javascript in there + + # SVG files get a slightly different CSP because they can embed resources and must not be framed. + # This regex location takes precedence over location /, so headers must be repeated here. location ~* \.svg$ { - add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';"; + add_header Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;" always; + add_header X-Content-Type-Options "nosniff" always; } error_log /var/log/nginx/parts.error.log; diff --git a/public/.htaccess b/public/.htaccess index a13baeee..0493298f 100644 --- a/public/.htaccess +++ b/public/.htaccess @@ -119,9 +119,14 @@ DirectoryIndex index.php -# Set Content-Security-Policy for svg files (and compressed variants), to block embedded javascript in there + # Set a strict CSP for all static assets not handled by PHP. + # PHP responses already carry their own CSP via NelmioSecurityBundle, so setifempty leaves those untouched. + Header always setifempty Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;" + Header always setifempty X-Content-Type-Options "nosniff" + + # SVG files get a slightly different CSP because they can embed resources and must not be framed. - Header set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none';" + Header always set Content-Security-Policy "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'none'; sandbox;" - \ No newline at end of file + diff --git a/src/Controller/AttachmentFileController.php b/src/Controller/AttachmentFileController.php index c3f581cc..7f48e661 100644 --- a/src/Controller/AttachmentFileController.php +++ b/src/Controller/AttachmentFileController.php @@ -122,7 +122,7 @@ class AttachmentFileController extends AbstractController private function setAttachmentCSPHeaders(Response $response): Response { //Set an CSP that disallow to run any scripts, styles or images from the attachment render page, as it is not used anywhere else for now and can be a security risk if used without proper precautions, so it should be opt-in - $response->headers->set('Content-Security-Policy', "default-src 'none'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self'; sandbox;"); + $response->headers->set('Content-Security-Policy', "default-src 'self'; script-src 'none'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; sandbox;"); return $response; }