4.5.2

Security patches

This release is a security hotfix. It only contains security fixes essential to addressing the CVE-2020-9280 security issue and follow up work from the CVE-2019-12245 patch which was released in September 2019.

Simply applying the patch might NOT be enough. Some files that were uploaded or created prior to the application of the CVE-2020-9280/CVE-2019-12245 patches may be left exposed to the public. You may need to run some additional file migration tasks to protect those files.

This release is especially important for Silverstripe CMS projects using the silverstripe/userforms module, but all project owners should consider upgrading as soon as convenient.

If your project was migrated from Silverstripe CMS 3, you need to run the new migrate-folders task.

As this release is a hotfix, it only includes updates to a subset of core modules. A full list of module versions included in Recipe 4.5.2 is provided below. We recommend referencing recipes in your dependencies, rather than individual modules, to simplify version tracking. See Recipes for more information.

Included module versions
ModuleVersion
silverstripe/admin1.5.1
silverstripe/asset-admin1.5.1
silverstripe/assets4.5.2
silverstripe/campaign-admin1.5.1
silverstripe/cms4.5.1
silverstripe/config1.0.18
silverstripe/errorpage1.5.1
silverstripe/framework4.5.3
silverstripe/graphql3.2.3
silverstripe/reports4.5.1
silverstripe/siteconfig4.5.1
silverstripe/versioned1.5.1
silverstripe/versioned-admin1.3.1

What does this release fix?

CVE-2020-9280: Folders migrated from Silverstripe CMS 3 may be unsafe to upload to (CVSS 5.9)

Files uploaded via Forms to folders migrated from Silverstripe CMS 3.x may be put to the default /Uploads folder instead. Uploads performed via the CMS UI are not affected. This is a security issue because the default /Uploads folder is publicly accessible by default, which means the uploaded files may be accessed by unauthorised parties via HTTP by guessing the file name. This affects installations which allowed upload folder protection via the optional silverstripe/secureassets module under 3.x. This module is installed and enabled by default on the Common Web Platform (CWP). The vulnerability only affects files uploaded after an upgrade to 4.x.

Read the CVE-2020-9280 security disclosure

CVE-2019-12245 follow up work

In September 2019, Silverstripe CMS 4.3.6 and 4.4.4 were released to address an issue that caused files to be stored in a publicly accessible location. Files that were uploaded via the userforms module could have been affected. Files uploaded via a custom form or programmatically created could also have been affected.

While those older releases prevented the exposure of new files, they did not retroactively protect files that had already been exposed prior to the patch. These new releases provide a mechanism to protect files wrongly exposed to the public.

What are the technical details of the vulnerabilities?

CVE-2020-9280

The root cause of the problem is that the file migration task would not create "Live" records for Folders migrated from Silverstripe CMS 3 prior to this patch. This may lead to inconsistencies between the database state and what the actual application gets from the ORM when performing its business logic.

This affects the silverstripe/userforms module and can cause user forms to save uploaded files in the default "Uploads" folder. Custom forms built with Silverstripe\Forms or Silverstripe\Assets components can also be susceptible to this behaviour.

Considering user forms as an example. Let's say you have an EditableFileField, that keeps an ID of a folder to upload to as an has_one relationship. When the submission happens with a Stage=Live context, the ORM cannot fetch the Folder instance, since it doesn't exist in the Live stage. When the upload Folder is undefined, the default Silverstripe\FileField implementation falls back to the default "Uploads" Folder, which is public by default.

CVE-2019-12245

Prior the 4.3.6/4.4.4 release, the File::canView() method could return an erroneous value for anonymous users when the file was being created or uploaded from a "Live" stage. When creating a new File in a Live stage, this could cause the file to be stored in a publicly accessible location when it was meant to be protected.

This could occur when:

  • a file was submitted in a user form upload field
  • a file was submitted in a custom form
  • a file was programmatically created by custom logic.

It would not occur when uploading a file via the asset administration area in the CMS.

How do I know if the files are actually exposed?

Even if your project creates files programmatically or receives them via a custom form, your files might not have been exposed. Whether your files have been exposed depends on your exact implementation.

There's two ways to verify if files are exposed:

  • look directly at the file system
  • try to access files in an incognito/private browser session.

If you have direct access to the file system or to a snapshot of your live data, you can look directly at the files. To do that, access your assets directory which will either be located in your project root or under the public directory. The assets directory will contain an hidden .protected directory which contains restricted and draft files. Other files which are not stored under the .protected directory will be visible to anonymous users. If you can see files that are meant to be confidential outside of the .protected folder, then they are accessible by the general public.

Some files may be stored under a hash path. This means that the physical files will be stored in a folder with a 10 character hexadecimal name. e.g.: If your File is stored under secret/folder/condfidential.pdf in the CMS, the physical file might be stored under a path similar to public/assets/secret/folder/c0f7cbd745/condfidential.pdf.

Learn what's the difference between natural paths and hash paths on the Silverstripe CMS documentation.

Alternatively, you can try accessing the file directly in a incognito/private browser session. To do so, navigate to a restricted file in the asset administration interface inside the CMS. On the right-hand panel, right-click on the file preview and copy the link to the file. If you paste the link into a private/incognito browser session and can access the file, then your file can be viewed by the general public.

Running the migration tasks

The 4.4.6/4.5.2 Silverstripe CMS releases add new file migration subtasks to retroactively protect files that have been wrongly exposed. They are 3 new subtasks.

  • migrate-folders creates versioning metadata for Silverstripe CMS 3 Folder records.
  • normalise-access protect files that have been wrongly exposed.
  • relocate-userform-uploads-2020-9280 move files uploaded through the userforms module to their intended folder.

Do I need to run the migration tasks?

Based on the nature of your Silverstripe CMS project, you may need to run all of the tasks, some of the them, or none of them.

You do not need to run any of the subtasks if the following three conditions are all true:

  • your project was not migrated from Silverstripe CMS 3
  • your project does not include the userforms module, or if it does, no userform has been configured to allow file uploads
  • your project does not programmatically create files or allow users to upload files via a custom form(s).

If your project was upgraded from a Silverstripe CMS 3 project, you should minimally run the migrate-folders subtask.

If your project programmatically creates files or allows users to upload files via a user form or a custom form, you should run the normalise-access task.

If your project allows users to upload files via a user form and was upgraded from a Silverstripe CMS 3 project, you may need to run all 3 tasks.

If your project was migrated from Silverstripe CMS 3 and allows users to upload files via custom forms, you should run the migrate-folders subtask. However, you also will need to manually protect the affected files. The easiest way could be to find all the folders affected through a SQL query before you run the task. Then you may be able to find all the custom forms using those folder IDs accordingly. Unfortunately, we cannot provide any automation for this scenario.

You should also consider making your /Uploads folder protected manually (via CMS) as a best practice, since it is publicly accessible by default.

What do the migration tasks do?

Take some time to read the following descriptions and understand how running each subtask will affect your Silverstripe CMS project. migrate-folders and normalise-access are relatively quick and have no side affects.

relocate-userform-uploads-2020-9280 is the most complex of the three subtasks and has the most potential side effects. You should take special care to understand what it does before running it.

migrate-folders subtask

The migrate-folders subtask validates that every "Folder" record in the File table has a matching entry in the File_Live table. This will prevent future file uploads from being accidentally stored in the publicly accessible "Uploads" folder.

It does not retroactively move files that have been stored in the wrong Folder to their intended destination.

To find out what Folders on your Silverstripe CMS project will be affected by running the migrate-folders subtask, you can run the following SQL query:

-- This query is targeted to MySQL. You may need to adapt it for other SQL databases.
SELECT
  File.ID,
  File.Name
FROM
  File
  LEFT JOIN
    File_Live
    ON File_Live.ID = File.ID
WHERE
  File_Live.ID IS NULL
  AND File.ClassName = 'SilverStripe\\Assets\\Folder'

Note that the file migration task will now run this step by default, so you won't need to explicitly run the migrate-folders task for future Silverstripe CMS 3 upgrades.

Learn how to migrate files from Silverstripe CMS 3 to Silverstripe CMS 4 on the Silverstripe CMS documentation.

normalise-access subtask

The normalise-access subtask goes through each individual File record in a Silverstripe CMS project and confirms that the canView result for anonymous users matches the physical location of the file. If the physical file is publicly visible while it's canView method returns false for anonymous users, it will be moved to the protected location.

relocate-userform-uploads-2020-9280 task

Because of the CVE-2020-9280 vulnerability, you may have Folders without a matching entries in their "Live" table. User form file upload submissions may end up being stored in an incorrect publicly-accessible location under the following conditions:

  • you have user forms that accept file uploads
  • the file upload field is configured to save the file in a folder that was created in Silverstripe CMS 3
  • the targeted folder has not been manually saved since your Silverstripe CMS 4 upgrade.

If those 3 conditions are met, you should consider running the relocate-userform-uploads-2020-9280 subtask.

relocate-userform-uploads-2020-9280 will go through each user form file upload submission and verify that the uploaded file is stored in the folder specified in the matching file upload user form field. If the file is stored in the wrong folder, the task will move the file to the Folder specified in the user form upload field configuration at the time the file was uploaded.

What if I have a custom upload form?

If you have a written a controller operating in the "Live" stage that creates files or receives uploaded files, and that controller is configured to save files in a folder created in Siverstripe CMS 3, your files may have been stored in the wrong location. Unfortunately, we can not provide a migration task for each custom scenario.

The relocate-userform-uploads-2020-9280 only works because there's a record of where the file was meant to be uploaded on the user form page.

If you have a small number of files, it might be practical to manually update their location via the asset administration interface in the CMS. Otherwise, you may have to write your own migration task to achieve the same purpose.

How do I run the migration tasks?

migrate-folders, normalise-access and relocate-userform-uploads-2020-9280 have been written as subtasks of the regular file migration task. The file migration task can be run in 3 different ways:

  • in the browser under dev/tasks
  • on the command line
  • as a queued job.

You can run each subtask individually or you can run all three subtasks in one go. The only parameter controls which subtasks will be run. You can get multiple subtask to run by specifying them as a comma-separated list: only=migrate-folders,normalise-access,relocate-userform-uploads-2020-9280. The subtasks are executed in a pre-defined sequence, so the order they appeared in the comma-separated list is irrelevant.

Which ever way you choose to run the sub tasks, you MUST run migrate-folders before relocate-userform-uploads-2020-9280. The recommended order to run the subtasks is:

  1. migrate-folders
  2. normalise-access
  3. relocate-userform-uploads-2020-9280.

Learn more about the File Migration Task on the Silverstripe CMS documentation

Testing the file migration task first

If possible, you should consider running the task in a development or testing environment first, either on a snapshot of your production environment or on a subset of your live data. Make sure you have back ups configured on your production environment and a recovery strategy in place in case things go badly.

Running the task in the browser

Running the file migration task in the browser should only be considered if you have a small number of files. It is susceptible to timeout if the task runs for too long. Depending on your application settings, you may not have the output logs of the tasks run. Please consider the other options first.

To run the task in the browser, navigate to /dev/tasks/MigrateFileTask and provide the only parameter as a GET parameter on your request. e.g.: https://example.com/dev/tasks/MigrateFileTask?only=migrate-folders

If you're running this task on a production environment, you will be asked to login using administrator credentials and you will be prompted to confirm you want to execute the task.

Running the task on the command line

Navigate to your project root on the command line and use the sake utility to execute the MigrateFileTask command.

./vendor/bin/sake dev/tasks/MigrateFileTask only=migrate-folders

Learn how to execute Silverstripe CMS tasks on the command line

Running the task as a queued job

If you've installed the queued-jobs module on your Silverstripe CMS project, you can run the file migration task as a queued job. There are two alternative ways to schedule a task to run as a queued job:

  • by navigating to the task URL in your browser (e.g.: https://example.com/dev/tasks/queue/MigrateFileTask?only=migrate-folders)
  • via the command line (e.g. vendor/bin/sake dev/tasks/queue/MigrateFileTask only=migrate-folders).

Once the task has been scheduled, you can monitor its progress in the queued job administration area inside the CMS.

How long should I expect the migration tasks to run for?

migrate-folders

The main factor in the execution time of the migrate-folders subtask is the number of Folders to migrate.

This subtask does not perform any filesystem operations. It mostly performs read and write SQL queries.

You can expect 20 to 30 Folders to be migrated per second. A project with 10,000 Folders to migrate should take less than 10 minutes.

normalise-access

The main factor in the execution time of the normalise-access subtask are:

  • the number of files to protect
  • the number of files in the same folder.

Protecting 1000 folders with 1 file each will be considerably faster than 1 folder with 1000 files. The total number of files in your project will also impact the subtask runtime, but to a lesser extent.

This substask performs a lot of file system operations with some read SQL queries. It does not write data to the database.

You can expect 10 to 20 files to be protected per seconds. Performance is degraded if all those files are in a small number of folders.

  • A project with 10,000 files to protected spread through a lot of folders should take less than an hour.
  • A project with 10,000 files to protected all in one folder can take several hours.

relocate-userform-uploads-2020-9280

The main factor in the execution time of the normalise-access subtask are:

  • the number of files to protect
  • the number of files in the same folder.

Moving 1000 folders with 1 file each will be considerably faster than 1 folder with 1000 files.

This substask performs a mix of file system operations and read/write SQL queries.

The approximate speed of the task is:

  • 500 files per minute with 1k files in the same folder (9 files per second)
  • 60 files per minute with 10k files in the same folder (1 file per second).

Edges cases and potential "gotchas"

I don't have "/Uploads" folder in CMS

Either the default "Uploads" folder was changed via Silverstripe\Assets\Upload::uploads_folder configuration parameter, or you don't have any files uploaded to it.

The issues covered by this patch may still affect your application.

I have two "/Uploads" folders in CMS

If you see two /Uploads folders in CMS, most likely that means one of them was migrated from Silverstripe CMS 3, whereas some application logic created the second copy after the migration.

In that case your database would contain a record in the File table for each of them, while File_Live table would only have a record about the other one.

After running the tasks provided in this patch, we suggest you manually move all content from one Folder to the other via the CMS. Then remove the empty folder.

What if I updated my files after upload?

Beware that draft files are present in 2 locations on the filesystem.

  • The original live version of the file is still available.
  • The draft version of the file should be present in a .protected folder that is inaccessible for unauthenticated users.

This means that even if you moved a file but don't publish it, the live version of the file will still be available.

To address this, "relocate-userform-uploads-2020-9280" task only works on the very first version of a file if it's published. Subsequent versions of the file are ignored by the task. For example, even if you didn't change the location of a file, but updated the filename or its permissions, the script will ignore that file.

If you have an unpublished draft, but the live version of the file is 1 (no published changes after upload), the script will try to move the live version of the file into its originally intended location, while preserving the current draft version of the file as-is. This would increment numbers for both Live and Draft versions of the file, while moving the live version to another location (both on the file system and in the CMS).

Files with draft access restriction

If you have a published file that is publicly available and you update the draft version to restrict read access, the live file currently remains accessible until you publish the draft version. However, the canView method on the live file will immediately start returning false for anonymous users.

This will cause normalise-access to protect the live file. Beware that if you have unpublished access restrictions on draft files, those will take effect on the live file after running normalise-access.

Read the "Files with access restriction in Draft" GitHub issue for more details.

Change Log

Bugfixes

  • 2020-03-09 c4709b6 Add FolderMigrationHelper
  • 2020-03-08 9779e4296 Register new sub tasks to fix files affected by CVE-2020-9280 and CVE-2019-12245 (Serge Latyntcev)
  • 2020-03-04 89e69ad Create NormaliseAccessMigrationHelper to fix files affected by CVE-2019-12245 (Maxime Rainville)