Image uploads causing information disclosure

Created on 18 June 2023, about 1 year ago
Updated 3 September 2023, 10 months ago

Originally reported to the Drupal security team by @andrewbelcher on 18 April 2018 (#166363). Assuming it affects the latest version, this issue's version is set to D10.0.

---
(Original post)

Due to the information disclosure we've encountered, I'm raising this as a security issue, though I'm not 100% sure it counts in that category.

The issue

We have a system that users submit a claim for compensation. This collects a number of details, including an image (image field) upload which contains supporting evidence. This supporting evidence can contain personally identifiable information.

We have had a couple occurrences where a customer has reported that the image to their claim is not the image they uploaded as part of their submission.

Upon investigation, these claims (and a few additional unreported cases) point to file entities where the URI is also in use by another file entity. The uploaded image is appropriate for the other claim.

So it looks somewhere, either two separate form submissions are creating file entities pointing to the same physical file. My assumption is that the second file is overwriting the first, though I'm not 100% certain about this yet.

Although the issue has re-occurred, we have not yet been able to reproduce it ourselves.

Implementation details

We are on Drupal 8.4.6, though this issue goes back a couple of months, so covers multiple versions of 8.4.x.

This is on a custom view mode of a custom content entity, SQL storage. The form object has sizable customisation, including other AJAX elements that result in form rebuild etc.

The form is accessible and submitted by both anonymous users and authenticated users.

The image field is standard unlimited cardinality image base field:

BaseFieldDefinition::create('image')
      ->setLabel('Upload')
      ->setTargetEntityTypeId('file')
      ->setDescription('')
      ->setRequired(TRUE)
      ->setSettings([
        'target_type' => 'file',
        'alt_field' => FALSE,
        'alt_field_required' => FALSE,
        'uri_scheme' => 'private',
      ])
      ->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
      ->setDisplayOptions('view', [
        'label' => 'above',
        'type' => 'image',
        'weight' => 12,
      ])
      ->setDisplayOptions('form', [
        'type' => 'image_image',
        'weight' => 5,
      ])
      ->setDisplayConfigurable('form', TRUE)
      ->setDisplayConfigurable('view', TRUE)

The form display uses the following config:

    type: image_image
    weight: 4
    settings:
      progress_indicator: throbber
      preview_image_style: thumbnail
    third_party_settings: {  }
    region: content

There is no customisation for the image upload on the form or from any form alters. Our theme adds some classes to the widget, but doesn't make any other adjustments.

Information from our investigation

An example in our file_managed table is:

310985	8914944b-99d9-4a52-81c9-349a13bdb054	en	0	image.jpg	private://2018-03/image_5950.jpg	image/jpeg	2304772	1	1522139633	1522139688
310999	f930313a-cbd5-4a6c-a840-7bd5dfebd94c	en	0	image.jpg	private://2018-03/image_5951.jpg	image/jpeg	2192873	1	1522139649	1522139697
311126	3013d0a8-6a6b-4cea-add8-dea4c1cb23b0	en	0	image.jpg	private://2018-03/image_5950.jpg	image/jpeg	2975758	1	1522139766	1522139798

So looking at the first/last rows, it looks like two users have uploaded a file named image.jpg (although we haven't had confirmation from a customer that the images uploaded did have the same name) at similar times (133 seconds interval) and both files have ended up pointing to the same physical file 2018-03/image_5950.jpg.

I initially thought it might be a race condition as the first example I looked at had identical creation times, but that isn't always the case (as in this example and some with days between them) and there are files added in between with the same filename that get correctly enumerated.

I considered caching, but our form implements the page cache kill switch (though we do have the dynamic page cache) and I would have wouldn't expect caching issues to have a gap in-between instances.

The issue affects both anonymous and authenticated users, though I have not yet seen an example where a single file has has multiple users (counting all anon as one user).

The earliest conflict was with file entities created on 2018-02-01 and 2018-02-20, with the last both on 2018-03-27 (the above example). I can't see any changes in our commit logs are related to either core or this particular form that give any indication as to why it started then, so I am assuming it is down to an increase in submission volume revealing the issue.

I have looked through the image widget and managed file element code, including tracking back to file_managed_file_save_upload()/file_save_upload()/file_destination(). While it looks conceivable that there could be a race condition between file_create_filename() and </code>drupal_move_uploaded_file() in file_save_upload(), the times of our examples indicate that this is not our problem. I cannot see anything else that looks like a candidate for causing our problem...

I cannot see anything in our logs (apache or watchdog) that sheds any light on the situation...

I'm now at a bit of a loss for where I go next in attempting to debug/reproduce/problem solve the issue - any suggestions would be very welcome!

Problem/Motivation

Steps to reproduce

Proposed resolution

Remaining tasks

User interface changes

API changes

Data model changes

Release notes snippet

Contributors

- andrewbelcher
- effulgentsia
- David Strauss
- mcdruid
- catch

Coordination:
- xjm
- cliefen

🐛 Bug report
Status

Active

Version

10.1

Component
File system 

Last updated 1 day ago

Created by

🇳🇱Netherlands dokumori Utrecht

Live updates comments and jobs are added and updated live.
  • Security

    It is used for security vulnerabilities which do not need a security advisory. For example, security issues in projects which do not have security advisory coverage, or forward-porting a change already disclosed in a security advisory. See Drupal’s security advisory policy for details. Be careful publicly disclosing security vulnerabilities! Use the “Report a security vulnerability” link in the project page’s sidebar. See how to report a security issue for details.

Sign in to follow issues

Comments & Activities

  • Issue created by @dokumori
  • 🇳🇱Netherlands dokumori Utrecht

    @catch's comment:

    The potential to replace deleted files seems very low impact and complex for an attacker to implement, for me it is a straight race condition/data integrity issue from that standpoint. Otherwise any race condition + data integrity issue would have to be handled in private.

    This is likely to require a database update, so it is something I think should be done in public, and only in a minor release of core.

    It's also mitigated by this 8.4.0 change that stops the automatic deletion of files with 0 usage (because file_usage was too unreliable, so files in use kept getting deleted at random).

    https://www.drupal.org/node/2891902

  • 🇳🇱Netherlands dokumori Utrecht

    Adding a tag and a list of contributors in the private issue

Production build 0.69.0 2024