Merge branch '4.4' into 4.5

This commit is contained in:
Maxime Rainville 2020-04-14 14:18:18 +12:00
commit de8fd82c55
4 changed files with 1026 additions and 27 deletions

View File

@ -18,28 +18,59 @@ $ ./vendor/bin/sake dev/tasks/MigrateFileTask
This task will perform a number of subtasks:
- `move-files`: Migrates existing `File` objects by adding required metadata to the database (incl. versioning).
By default, it will not move files on the filesystem (starting with [4.4.0](/changelogs/4.4.0)).
Publishes to the live stage to ensure
that previously visible assets remain visible to the public site.
If additional security or visibility rules should be applied to `File`, then
make sure to correctly extend `canView` via extensions.
- `move-thumbnails`: Move existing thumbnails, rather than have them generated on the fly.
This task is optional, but helps to avoid growing your asset folder (no duplicate thumbnails)
- `generate-cms-thumbnails`: The new CMS UI needs different thumbnail sizes, which can be pregenerated.
This can be a CPU and memory intensive task for large asset stores.
See [Migrating substantial number of files](#performance)
- `fix-secureassets`: Migrates files secured through the [silverstripe/secureassets](https://github.com/silverstripe/silverstripe-secureassets) module.
Ensures that previous `.htaccess` folder protections don't interfere with 4.x-style asset protections.
- `fix-folder-permissions`: Fixes folder permissions which might have been broken by
previously using the [silverstripe/secureassets](https://github.com/silverstripe/silverstripe-secureassets)
- `move-files`: Migrates existing `File` objects by adding required metadata to the database (incl. versioning).
By default, it will not move files on the filesystem (starting with [4.4.0](/changelogs/4.4.0)).
Publishes to the live stage to ensure
that previously visible assets remain visible to the public site.
If additional security or visibility rules should be applied to `File`, then
make sure to correctly extend `canView` via extensions.
- `migrate-folders`: Migrates existing `Folder` objects by adding metadata to the
database (incl. versioning). This subtask does not perform any operations with the actual
file system. It merely updates the database records.
- `move-thumbnails`: Move existing thumbnails, rather than have them generated on the fly.
This task is optional, but helps to avoid growing your asset folder (no duplicate thumbnails)
- `generate-cms-thumbnails`: The new CMS UI needs different thumbnail sizes, which can be pregenerated.
This can be a CPU and memory intensive task for large asset stores.
See [Migrating substantial number of files](#performance)
- `fix-secureassets`: Migrates files secured through the [silverstripe/secureassets](https://github.com/silverstripe/silverstripe-secureassets) module.
Ensures that previous `.htaccess` folder protections don't interfere with 4.x-style asset protections.
- `fix-folder-permissions`: Fixes folder permissions which might have been broken by
previously using the [silverstripe/secureassets](https://github.com/silverstripe/silverstripe-secureassets)
One or more subtasks can be run individually through the `only` argument.
Example: `only=move-files,move-thumbnails`
The `move-files` and `migrate-folders` subtasks are mandatory. Without running them,
your database will be left in an inconsistent state.
The output is quite verbose by default. Look for `WARNING` and `ERROR` in the log files.
When executing the task on CLI, you'll get colour coded error messages.
### Specialised opt-in file migration subtasks
Some subtasks are only necessary in specific situations. Those subtasks will not be run by default and must be explicitly referenced via the `only` parameter.
- `normalise-access`: This subtask identifies physical files that are
inadvertently exposed to the public and moves them to a protected location.
Until the release of Silverstripe CMS 4.3.5/4.4.4 in September 2019,
Silverstripe CMS stored protected files in publicly accessible locations
when the files were created from a "LIVE" versioned context. This commonly
occur when a user uploaded a file in a user form.
[CVE-2019-12245 patch](https://www.silverstripe.org/download/security-releases/CVE-2019-12245)
addressed this issue preventing the exposure of new files. However, this patch
did not retroactively protect files that had been exposed. You should run this
task at least once if your Silverstripe CMS project was created prior to
September 2019.
- `relocate-userform-uploads-2020-9280`: This subtask is specific to the
`silverstripe/userforms` module. Because of the
[CVE-2020-9280](https://www.silverstripe.org/download/security-releases/CVE-2020-9280)
vulnerability, if your project was migrated from Silverstripe CMS 3, the
migrated user forms might upload files in wrong folders. This subtask goes
through all files uploaded via user forms and moves them to their intended
locations.
Read the [Silverstripe CMS 4.4.6 change logs](/changelogs/4.4.6) to learn more
about the `normalise-access` and `relocate-userform-uploads-2020-9280` subtasks.
## Background migration through the Queuedjobs module
In general, it's safest to run the file migration on a non-production environment,
@ -62,7 +93,7 @@ are configured to run jobs automatically without time limits
[2](https://www.cwp.govt.nz/developer-docs/en/2/working_with_projects/infrastructural_considerations/)).
It is not recommended to run
[multiple processes](https://github.com/symbiote/silverstripe-queuedjobs/blob/master/docs/en/configuring-multi-process-execution.md)
when executing the file migration job.
when executing the file migration job.
## Migration of existing thumbnails
@ -120,7 +151,7 @@ and [File Security: Protected file headers](file_security#protected_file_headers
## Migrating substantial numbers of files {#performance}
The time it takes to run the file migration will depend on the number of files and their size. The generation of thumbnails will depend on the number and dimension of your images.
In general, the migration task can be restarted if it times out, and will continue where it left off.
In general, the migration task can be restarted if it times out, and will continue where it left off.
If you are migrating a substantial number of files, you should run the file migration task either as a queued job or on the command line. If the migration task fails or times out, you can start it again and it will pick up where it left off.
@ -128,7 +159,7 @@ If your environment supports the _Imagick_ PHP library, you may want to use that
[Changing the image manipulation driver to Imagick](images#changing-the-manipulation-driver-to-imagick)
If your project hosts big images (e.g. 4K images), this can also affect the amount of memory used to generate the thumbnails. The file migration task assumes that it will have at least 512MB of memory available.
If your project hosts big images (e.g. 4K images), this can also affect the amount of memory used to generate the thumbnails. The file migration task assumes that it will have at least 512MB of memory available.
By default the file migration task will not generate thumbnails for files greater than 9MB to avoid exhausting the available memory. To increase this limit, add the following code to your YML configuration:

View File

@ -0,0 +1,455 @@
# 4.4.6
## 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.
* [Read the CVE-2020-9280 security disclosure](https://www.silverstripe.org/download/security-releases/CVE-2020-9280)
from April 2020
* [Read the CVE-2019-12245 security disclosure](https://www.silverstripe.org/download/security-releases/CVE-2019-12245)
from 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.
## 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](https://www.silverstripe.org/download/security-releases/CVE-2020-9280)
### 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.
* [Read the CVE-2019-12245 security disclosure](https://www.silverstripe.org/download/security-releases/CVE-2019-12245)
* [Read the Silverstripe CMS 4.3.6 change logs](4.3.6)
* [Read the Silverstripe CMS 4.4.4 change logs](4.4.4)
## 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_](https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/#natural-path-vs-hash-path)
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:
```sql
-- 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](https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/#migration-from-silverstripe-3-to-silverstripe-4-4-or-later)
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](https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/)
#### 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.
```bash
./vendor/bin/sake dev/tasks/MigrateFileTask only=migrate-folders
```
[Learn how to execute Silverstripe CMS tasks on the command line](https://docs.silverstripe.org/en/4/developer_guides/cli/)
#### Running the task as a queued job
If you've installed the
[queued-jobs module](https://github.com/symbiote/silverstripe-queuedjobs) 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](https://api.silverstripe.org/4/SilverStripe/Assets/Upload.html)
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](https://github.com/silverstripe/silverstripe-assets/issues/385)
for more details.
<!--- Changes below this line will be automatically regenerated -->
## Change Log
### Bugfixes
* 2020-03-09 [6779fd3](https://github.com/silverstripe/silverstripe-assets/commit/6779fd3c8c1c05a3db5035bf6e541c9483d161fc) Add FolderMigrationHelper
* 2020-03-08 [b269d8749](https://github.com/silverstripe/silverstripe-framework/commit/b269d874909cd70bb60c1a2974ea5446b43b0436) Register new sub tasks to fix files affected by CVE-2020-9280 and CVE-2019-12245 (Serge Latyntcev)
* 2020-03-04 [12ea7cd](https://github.com/silverstripe/silverstripe-assets/commit/12ea7cd2037bebcb3196dd5e3aaa72e6dbc7c7b2) Create NormaliseAccessMigrationHelper to fix files affected by CVE-2019-12245 (Maxime Rainville)
<!--- Changes above this line will be automatically regenerated -->

View File

@ -0,0 +1,448 @@
# 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.
* [Read the CVE-2020-9280 security disclosure](https://www.silverstripe.org/download/security-releases/CVE-2020-9280)
from April 2020
* [Read the CVE-2019-12245 security disclosure](https://www.silverstripe.org/download/security-releases/CVE-2019-12245)
from 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.
## 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](https://www.silverstripe.org/download/security-releases/CVE-2020-9280)
### 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.
* [Read the CVE-2019-12245 security disclosure](https://www.silverstripe.org/download/security-releases/CVE-2019-12245)
* [Read the Silverstripe CMS 4.3.6 change logs](4.3.6)
* [Read the Silverstripe CMS 4.4.4 change logs](4.4.4)
## 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_](https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/#natural-path-vs-hash-path)
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:
```sql
-- 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](https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/#migration-from-silverstripe-3-to-silverstripe-4-4-or-later)
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](https://docs.silverstripe.org/en/4/developer_guides/files/file_migration/)
#### 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.
```bash
./vendor/bin/sake dev/tasks/MigrateFileTask only=migrate-folders
```
[Learn how to execute Silverstripe CMS tasks on the command line](https://docs.silverstripe.org/en/4/developer_guides/cli/)
#### Running the task as a queued job
If you've installed the
[queued-jobs module](https://github.com/symbiote/silverstripe-queuedjobs) 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](https://api.silverstripe.org/4/SilverStripe/Assets/Upload.html)
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](https://github.com/silverstripe/silverstripe-assets/issues/385)
for more details.
<!--- Changes below this line will be automatically regenerated -->
<!--- Changes above this line will be automatically regenerated -->

View File

@ -9,6 +9,8 @@ use Psr\Log\LoggerInterface;
use SilverStripe\AssetAdmin\Helper\ImageThumbnailHelper;
use SilverStripe\Assets\Dev\Tasks\LegacyThumbnailMigrationHelper;
use SilverStripe\Assets\Dev\Tasks\FileMigrationHelper;
use SilverStripe\Assets\Dev\Tasks\FolderMigrationHelper;
use SilverStripe\Assets\Dev\Tasks\NormaliseAccessMigrationHelper;
use SilverStripe\Assets\Storage\AssetStore;
use SilverStripe\Assets\Storage\FileHashingService;
use SilverStripe\Control\Director;
@ -17,6 +19,7 @@ use SilverStripe\Core\Injector\Injector;
use SilverStripe\Logging\PreformattedEchoHandler;
use SilverStripe\Dev\BuildTask;
use SilverStripe\Assets\Dev\Tasks\SecureAssetsMigrationHelper;
use SilverStripe\UserForms\Task\RecoverUploadLocationsHelper;
use \Bramus\Monolog\Formatter\ColoredLineFormatter;
/**
@ -30,12 +33,18 @@ class MigrateFileTask extends BuildTask
protected $defaultSubtasks = [
'move-files',
'migrate-folders',
'move-thumbnails',
'generate-cms-thumbnails',
'fix-folder-permissions',
'fix-secureassets',
];
protected $optInSubtasks = [
'normalise-access',
'relocate-userform-uploads-2020-9280'
];
private static $dependencies = [
'logger' => '%$' . LoggerInterface::class,
];
@ -81,13 +90,24 @@ class MigrateFileTask extends BuildTask
->setLogger($this->logger)
->run();
// TODO Split file migration helper into two tasks,
// and report back on their process counts consistently here
// if ($count) {
// $this->logger->info("{$count} File objects upgraded");
// } else {
// $this->logger->info("No File objects needed upgrading");
// }
$this->extend('postFileMigrationSubtask', $subtask);
}
}
$subtask = 'migrate-folders';
if (in_array($subtask, $subtasks)) {
if (!class_exists(FolderMigrationHelper::class)) {
$this->logger->error("No folder migration helper detected");
} else {
$this->extend('preFileMigrationSubtask', $subtask);
$this->logger->notice("######################################################");
$this->logger->notice("Migrating folder database records ({$subtask})");
$this->logger->notice("######################################################");
FolderMigrationHelper::singleton()
->setLogger($this->logger)
->run();
$this->extend('postFileMigrationSubtask', $subtask);
}
@ -195,6 +215,46 @@ class MigrateFileTask extends BuildTask
}
}
$subtask = 'normalise-access';
if (in_array($subtask, $subtasks)) {
if (!class_exists(NormaliseAccessMigrationHelper::class)) {
$this->logger->error("No normalise access migration helper detected");
} else {
$this->extend('preFileMigrationSubtask', $subtask);
$this->logger->notice("######################################################");
$this->logger->notice("Migrating filesystem and database records ({$subtask})");
$this->logger->notice("######################################################");
NormaliseAccessMigrationHelper::singleton()
->setLogger($this->logger)
->run();
$this->extend('postFileMigrationSubtask', $subtask);
}
}
$subtask = 'relocate-userform-uploads-2020-9280';
if (in_array($subtask, $subtasks)) {
if (!class_exists(RecoverUploadLocationsHelper::class)) {
$this->logger->error("No UserForms helper detected");
} else {
$this->extend('preFileMigrationSubtask', $subtask);
$this->logger->notice("######################################################");
$this->logger->notice("Recovering UserForm uploaded file locations ({$subtask})");
$this->logger->notice("######################################################");
RecoverUploadLocationsHelper::singleton()
->setLogger($this->logger)
->run();
$this->extend('postFileMigrationSubtask', $subtask);
}
}
$this->logger->info("Done!");
$this->extend('postFileMigration');
$this->logger->info("Done!");
@ -236,8 +296,13 @@ TXT;
protected function validateArgs($args)
{
if (!empty($args['only'])) {
if (array_diff(explode(',', $args['only']), $this->defaultSubtasks)) {
throw new \InvalidArgumentException('Invalid subtasks detected: ' . $args['only']);
$only = explode(',', $args['only']);
$diff = array_diff($only, $this->defaultSubtasks);
$diff = array_diff($diff, $this->optInSubtasks);
if ($diff) {
throw new \InvalidArgumentException('Invalid subtasks detected: ' . implode(', ', $diff));
}
}
}