In Drupal, hook_file_download() is used to control access to private files. It operates on the principle that if one module wants to grant access and another module wants to deny access, the module which wants to deny access wins.
The File module implements this hook to control access to file attachments. It denies access to a file whenever all the entities that the file is attached to are not accessible by the current user.
In Drupal 7, a key part of this system is that when a file is no longer being used it is immediately deleted. This means that even after the File module stops managing a private file, it still ensures that users who did not have access to the file before will not suddenly get access to it.
In Drupal 8, this behavior was removed (I think in #1401558: Remove the usage handling logic from file_delete()) which introduces a security issue. The security issue is not present in Drupal 7, but can be reproduced if using the core patch at #1399846-263: Make unused file 'cleanup' configurable since that proposes backporting a similar change to Drupal 7. The instructions below will work for either Drupal 8 or Drupal 7 with the above patch applied; given that a contrib module is required to reproduce this, it is a little easier to understand with the Drupal 7 version.
To reproduce from a fresh installation:
- Configure image fields on articles to be private files.
- Ensure that a module is installed which will grant anonymous users access to these files. In Drupal 7, this could be done most simply with something like https://www.drupal.org/project/file_entity and granting the "View private files" permission to anonymous users. In Drupal 8, the attached patch (https://www.drupal.org/files/issues/2461845-demo-do-not-test.patch) can be applied to Drupal core for testing purposes.
-
Create an unpublished article node with an image attached. Because the article is unpublished and anonymous users can't view unpublished content, they should not have access to the image either. You can verify that this works; if anonymous users access the image file directly by URL, they will not have access to it.
The intention of this setup is as follows: The site owner wants to make sure anonymous users can view a whole class of private files by default. But if the private file is associated with restricted content, it should inherit those restrictions and not be viewable by everyone. (For example, the image might be a top secret diagram that is not ready for the public to view.)
-
Now remove the file from the node, or delete the node (either should work). If you try to view the file as an anonymous user, you will now suddenly have access to it. This is a security issue. It will only be accessible for 6 hours by default, but because of #1399846: Make unused file 'cleanup' configurable the window can be configured to be longer than that, including infinitely long (via the "Delete orphaned files after" setting at admin/config/media/file-system).
An unpatched Drupal 7 site deletes the file right away, so does not experience this security issue.
I am not sure how best to fix this. Rolling back the changes from #1401558: Remove the usage handling logic from file_delete() would probably do it but doesn't sound like a great idea. Maybe there needs to be code to more aggressively block access to files that are not attached to any entity anymore?