From 8c91bd76a5052ddf3e3ab9fd8333f9aa7b2e2dd6 Mon Sep 17 00:00:00 2001 From: Leonardo Tegon Date: Thu, 19 Sep 2019 13:39:38 -0300 Subject: [PATCH] Don't call `#send` in form object to build file inputs Before this commit, Simple Form was calling `#send` in the form object to check whether the resulting object was an attachment. That made the library open to DOS, information disclousure and execution of unintended action attacks if a form was built with user input. ```erb <%= simple_form_for @user do |f| %> <%= f.label @user_supplied_string %> ... <% end %> ``` The solution is try to figure out if an input is of type file by checking for methods present in the most popular Ruby Gems for file uploads. The current supported Gems are: `activestorage`, `carrierwave`, `paperclip`, `shrine` and `refile`. The code is relying on public APIs so it should be fine for now. It would be nice to have a single API to perform this check, so we'll suggest one for those libraries. Co-Authored-By: Felipe Renan --- CHANGELOG.md | 14 ++++++++ Gemfile.lock | 4 +-- .../config/initializers/simple_form.rb | 3 -- lib/simple_form.rb | 25 +++++++++++--- lib/simple_form/form_builder.rb | 23 +++++++++++-- lib/simple_form/version.rb | 2 +- test/form_builder/general_test.rb | 33 ++++++++----------- test/support/models.rb | 18 ++++++++++ test/test_helper.rb | 1 + 9 files changed, 91 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d5d614a5..e69f643ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ ## Unreleased +## 5.0.0 + ### Enhancements * Set multiple attribute for grouped selects also. [@ollym](https://github.com/ollym) * Removes or renames label classes. [Abduvakilov](https://github.com/Abduvakilov) @@ -7,6 +9,18 @@ * Update bootstrap generator template to match v4.3.x. [@m5o](https://github.com/m5o) * Allow "required" attribute in generated select elements of PriorityInput. [@mcountis](https://github.com/mcountis) +### Bug fix +* Do not call `#send` in form object to check whether the attribute is a file input. [@tegon](https://github.com/tegon) + +## Deprecations +* The config `SimpleForm.file_methods` is deprecated and it has no effect. Simple Form now supports automatically discover of file inputs for the following Gems: activestorage, carrierwave, paperclip, refile and shrine. If you are using a custom method that is not from one of the supported Gems, please change your forms to pass the input type explicitly: + +```erb + <%= form.input :avatar, as: :file %> + ``` + +See http://blog.plataformatec.com.br/2019/09/incorrect-access-control-in-simple-form-cve-2019-16676 for more information. + ## 4.1.0 ### Enhancements diff --git a/Gemfile.lock b/Gemfile.lock index 9f3964863..50bd0680f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,7 +1,7 @@ PATH remote: . specs: - simple_form (4.1.0) + simple_form (5.0.0) actionpack (>= 5.0) activemodel (>= 5.0) @@ -299,4 +299,4 @@ DEPENDENCIES simple_form! BUNDLED WITH - 1.17.1 + 1.17.3 diff --git a/lib/generators/simple_form/templates/config/initializers/simple_form.rb b/lib/generators/simple_form/templates/config/initializers/simple_form.rb index bb43c3364..0dc88e8c1 100644 --- a/lib/generators/simple_form/templates/config/initializers/simple_form.rb +++ b/lib/generators/simple_form/templates/config/initializers/simple_form.rb @@ -129,9 +129,6 @@ # change this configuration to true. config.browser_validations = false - # Collection of methods to detect if a file type was given. - # config.file_methods = [ :mounted_as, :file?, :public_filename, :attached? ] - # Custom mappings for input types. This should be a hash containing a regexp # to match as key, and the input type that will be used when the field name # matches the regexp as value. diff --git a/lib/simple_form.rb b/lib/simple_form.rb index 452e87b3b..2f603e8a0 100644 --- a/lib/simple_form.rb +++ b/lib/simple_form.rb @@ -38,6 +38,17 @@ def %{name}(wrapper_options) See https://github.com/plataformatec/simple_form/pull/997 for more information. WARN + FILE_METHODS_DEPRECATION_WARN = <<-WARN +[SIMPLE_FORM] SimpleForm.file_methods is deprecated and has no effect. + +Since version 5, Simple Form now supports automatically discover of file inputs for the following Gems: activestorage, carrierwave, paperclip, refile and shrine. +If you are using a custom method that is not from one of the supported Gems, please change your forms to pass the input type explicitly: + + <%= form.input :avatar, as: :file %> + +See http://blog.plataformatec.com.br/2019/09/incorrect-access-control-in-simple-form-cve-2019-16676 for more information. + WARN + @@configured = false def self.configured? #:nodoc: @@ -120,10 +131,6 @@ def self.configured? #:nodoc: mattr_accessor :browser_validations @@browser_validations = true - # Collection of methods to detect if a file type was given. - mattr_accessor :file_methods - @@file_methods = %i[mounted_as file? public_filename attached?] - # Custom mappings for input types. This should be a hash containing a regexp # to match as key, and the input type that will be used when the field name # matches the regexp as value, such as { /count/ => :integer }. @@ -265,6 +272,16 @@ def self.form_class=(value) @@form_class = value end + def self.file_methods=(file_methods) + ActiveSupport::Deprecation.warn(FILE_METHODS_DEPRECATION_WARN, caller) + @@file_methods = file_methods + end + + def self.file_methods + ActiveSupport::Deprecation.warn(FILE_METHODS_DEPRECATION_WARN, caller) + @@file_methods + end + # Default way to setup Simple Form. Run rails generate simple_form:install # to create a fresh initializer with all configuration values. def self.setup diff --git a/lib/simple_form/form_builder.rb b/lib/simple_form/form_builder.rb index 5f1f2e5ad..4e8a73cc3 100644 --- a/lib/simple_form/form_builder.rb +++ b/lib/simple_form/form_builder.rb @@ -572,9 +572,28 @@ def find_custom_type(attribute_name) }.try(:last) if SimpleForm.input_mappings end + # Internal: Try to discover whether an attribute corresponds to a file or not. + # + # Most upload Gems add some kind of attributes to the ActiveRecord's model they are included in. + # This method tries to guess if an attribute belongs to some of these Gems by checking the presence + # of their methods using `#respond_to?`. + # + # Note: This does not support multiple file upload inputs, as this is very application-specific. + # + # The order here was choosen based on the popularity of Gems and for commodity - e.g. the method + # with the suffix `_url` is present in three Gems, so it's checked with priority: + # + # - `#{attribute_name}_attachment` - ActiveStorage >= `5.2` and Refile >= `0.2.0` <= `0.4.0` + # - `#{attribute_name}_url` - Shrine >= `0.9.0`, Refile >= `0.6.0` and CarrierWave >= `0.2.1` + # - `#{attribute_name}_attacher` - Refile >= `0.4.0` and Shrine >= `0.9.0` + # - `#{attribute_name}_file_name` - Paperclip ~> `2.0` (added for backwards compatibility) + # + # Returns a Boolean. def file_method?(attribute_name) - file = @object.send(attribute_name) if @object.respond_to?(attribute_name) - file && SimpleForm.file_methods.any? { |m| file.respond_to?(m) } + @object.respond_to?("#{attribute_name}_attachment") || + @object.respond_to?("#{attribute_name}_url") || + @object.respond_to?("#{attribute_name}_attacher") || + @object.respond_to?("#{attribute_name}_file_name") end def find_attribute_column(attribute_name) diff --git a/lib/simple_form/version.rb b/lib/simple_form/version.rb index 7cba90867..53f698631 100644 --- a/lib/simple_form/version.rb +++ b/lib/simple_form/version.rb @@ -1,4 +1,4 @@ # frozen_string_literal: true module SimpleForm - VERSION = "4.1.0".freeze + VERSION = "5.0.0".freeze end diff --git a/test/form_builder/general_test.rb b/test/form_builder/general_test.rb index f8732514e..196a46fb9 100644 --- a/test/form_builder/general_test.rb +++ b/test/form_builder/general_test.rb @@ -239,31 +239,24 @@ def with_custom_form_for(object, *args, &block) assert_select 'form select#user_updated_at_1i.datetime' end - test 'builder generates file for file columns' do - @user.avatar = MiniTest::Mock.new - @user.avatar.expect(:public_filename, true) - @user.avatar.expect(:!, false) - - with_form_for @user, :avatar - assert_select 'form input#user_avatar.file' + test 'builder generates file input for ActiveStorage >= 5.2 and Refile >= 0.2.0 <= 0.4.0' do + with_form_for UserWithAttachment.build, :avatar + assert_select 'form input#user_with_attachment_avatar.file' end - test 'builder generates file for activestorage entries' do - @user.avatar = MiniTest::Mock.new - @user.avatar.expect(:attached?, false) - @user.avatar.expect(:!, false) - - with_form_for @user, :avatar - assert_select 'form input#user_avatar.file' + test 'builder generates file input for Shrine >= 0.9.0, Refile >= 0.6.0 and CarrierWave >= 0.2.1' do + with_form_for UserWithAttachment.build, :cover + assert_select 'form input#user_with_attachment_cover.file' end - test 'builder generates file for attributes that are real db columns but have file methods' do - @user.home_picture = MiniTest::Mock.new - @user.home_picture.expect(:mounted_as, true) - @user.home_picture.expect(:!, false) + test 'builder generates file input for Refile >= 0.4.0 and Shrine >= 0.9.0' do + with_form_for UserWithAttachment.build, :profile_image + assert_select 'form input#user_with_attachment_profile_image.file' + end - with_form_for @user, :home_picture - assert_select 'form input#user_home_picture.file' + test 'builder generates file input for Paperclip ~> 2.0' do + with_form_for UserWithAttachment.build, :portrait + assert_select 'form input#user_with_attachment_portrait.file' end test 'build generates select if a collection is given' do diff --git a/test/support/models.rb b/test/support/models.rb index 9801a7a2c..82530de7e 100644 --- a/test/support/models.rb +++ b/test/support/models.rb @@ -333,3 +333,21 @@ def name class UserNumber1And2 < User end + +class UserWithAttachment < User + def avatar_attachment + OpenStruct.new + end + + def cover_url + "/uploads/cover.png" + end + + def profile_image_attacher + OpenStruct.new + end + + def portrait_file_name + "portrait.png" + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 0dccc0212..8797e93a5 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -91,4 +91,5 @@ def company_user_path(*args) alias :validating_user_path :user_path alias :validating_users_path :user_path alias :other_validating_user_path :user_path + alias :user_with_attachment_path :user_path end