From 5d186f3311960f7da5e7e20edb0e4cc0576c816a Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 28 Oct 2026 17:08:14 +1300 Subject: [PATCH] DOC Update apache and nginx documentation --- .../14_Files/03_File_Security.md | 69 +++++++++++++++---- 1 file changed, 55 insertions(+), 14 deletions(-) diff --git a/en/02_Developer_Guides/14_Files/03_File_Security.md b/en/02_Developer_Guides/14_Files/03_File_Security.md index a2f5e5be9..959c1aef5 100644 --- a/en/02_Developer_Guides/14_Files/03_File_Security.md +++ b/en/02_Developer_Guides/14_Files/03_File_Security.md @@ -481,23 +481,43 @@ RewriteRule .* ../index.php [QSA] You will need to ensure that your core apache configuration has the necessary `AllowOverride` settings to support the local `.htaccess` file. -Although assets have a 404 handler which routes to a PHP handler, `.php` files within assets itself -should not be allowed to be marked as executable. +Although assets have a 404 handler which routes to a PHP handler, the webserver must never execute +`.php` files within the assets directory. The `public/assets/.htaccess` file, which `silverstripe/assets` +generates from the `PublicAssetAdapter_HTAccess` template on `?flush`, denies direct access to script +files with the following section: -When securing your server you should ensure that you protect against both files that can be uploaded as -executable on the server, as well as protect against accidental upload of `.htaccess` which bypasses -this file security. +```text + + Require all denied + +``` -For instance your server configuration should look similar to the below: +Apache applies this denial before PHP runs, so it works with `mod_php`, php-fpm and CGI. -```xml - - php_admin_flag engine off +An uploaded `.htaccess` file can override it. If `AllowOverride` includes `AuthConfig`, the uploaded file +can declare its own `` block with `Require all granted`, and that block takes precedence over +the one in the shipped `.htaccess`. The shipped `.htaccess` needs `AuthConfig` for its own +`Require all denied` line, so you cannot remove it while that file is in use. + +We recommend that you repeat the denial in your core Apache configuration, so that it still applies if +the generated `.htaccess` is missing or damaged, and turn the PHP engine off for the directory: + +```text + + + php_admin_flag engine off + + + + Require all denied + ``` -The `php_admin_flag` will protect against uploaded `.htaccess` files accidentally re-enabling script -execution within the assets directory. +The `php_admin_flag engine off` line protects servers that run `mod_php`. Apache does not let a +`.htaccess` file change an `admin` flag, so an uploaded file cannot turn the PHP engine back on. The +`` wrapper makes sure Apache still starts when `mod_php` is not loaded. The line has no effect +under php-fpm. #### Windows IIS 7.5+ @@ -526,12 +546,33 @@ to false, to allow these to take effect. If using a server configuration which must be configured outside of the web or asset root, you will need to make sure you manually configure these rules. -For instance, this will allow your nginx site to serve files directly, while ensuring -dynamic requests are processed via the Framework: +For instance, this will allow your nginx site to serve existing files directly while routing other +requests through the Framework. It also stops nginx from passing scripts in the assets directory to +php-fpm, and stops it from serving protected files directly: ```text location ^~ /assets/ { sendfile on; - try_files $uri index.php?$query_string; + try_files $uri /index.php?$query_string; + + # Do not pass scripts in the assets directory to PHP + location ~* \.(?:php[0-9]*|phar|phtml|pht|inc)$ { + return 403; + } +} + +# Protected files are served only through the Framework, never directly +location ^~ /assets/.protected/ { + return 403; } ``` + +The `^~` modifier is important. It stops nginx from checking server-level regular expression locations, +such as a `location ~ \.php$` block that passes requests to php-fpm, for any URL under `/assets/`. Without +it, that block wins over the prefix location and nginx passes the uploaded script to php-fpm. + +This blocks the same script extensions as the generated Apache `.htaccess`. The Apache file goes further. +Silverstripe CMS generates it from the `PublicAssetAdapter_HTAccess` template and inserts an allowlist of +the extensions in `File.allowed_extensions`, plus `css`, `js` and the static error pages. Apache serves +only files that match this allowlist. Silverstripe CMS does not generate nginx configuration, so this +example has no allowlist. Add one if you need the same behaviour.