Simple Form version 5.0
was released today with a fix for a security issue that could allow an attacker to execute methods on form objects. The issue is explained in details below.
Description
The issue applies only to forms that are built using user-supplied input. For example, the following form that builds a label based on user input:
<%= simple_form_for @user do |form| %>
<%= form.label @user_supplied_string %>
...
<% end %>
Code language: HTML, XML (xml)
In this case, the @user_supplied_string
would be invoked as a method call in the @user
object (unless the string contains any of the following: password
, time_zone
, country
, email
, phone
or url
).
If you build your forms with backend-provided information only, your application is not affected by this issue.
Possible implications
By knowing that this breach exists, an attacker could invoke any method on the form object. This means that they could do any of the following:
- Code execution (call unintended actions like
#destroy
) - Denial of Service (by executing computation-intensive actions)
- Information Disclosure (check the presence of methods, leak user information)
Cause
The issue is caused by Simple Form’s automatically discover of input types feature. When a form input is provided without the as
option, the library tries to discover which type that input is. This is done with a regular expression for the most common types. Something like this:
case attribute_name.to_s
when /(?:\b|\W|_)password(?:\b|\W|_)/ then :password
when /(?:\b|\W|_)time_zone(?:\b|\W|_)/ then :time_zone
when /(?:\b|\W|_)country(?:\b|\W|_)/ then :country
when /(?:\b|\W|_)email(?:\b|\W|_)/ then :email
when /(?:\b|\W|_)phone(?:\b|\W|_)/ then :tel
when /(?:\b|\W|_)url(?:\b|\W|_)/ then :url
Code language: JavaScript (javascript)
This works for a bunch of input types but it doesn’t for file inputs. In the case of file inputs, they can have a lot of different names – avatar
, attachment
, profile_image
and so on.
In order to discover file inputs, Simple Form was calling #send
on the object passing attribute_name
as the parameter. That would result in an object, which would then later be checked against a list of file methods, to decide whether that attribute should be a file input or not:
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) } end
The default value of the SimpleForm.file_methods
config was: [:mounted_as, :file?, :public_filename, :attached?]
which are basically methods present in some popular file upload Gems.
Solution
Simple Form was changed to check in a different way whether some attribute might be suitable for a file input. It now checks for the presence of methods directly, without calling #send
. For example, the check for ActiveStorage looks like this:
@object.respond_to?("#{attribute_name}_attachment")
Code language: CSS (css)
The officially supported Gems are:
- ActiveStorage >= 5.2
- Carrierwave >= 0.2.1
- Refile >= 0.2.0
- Shrine >= 0.9.0
- Paperclip >= 2.0 (for backwards compatibility)
Although this new code is harder to maintain, we think it’s worth the tradeoff with more security. See the commit with the solution for more information.
Note: This solution does not support multiple file upload inputs, as this is very application-specific. To render file input for multiple file upload, use the as
option.
How to upgrade
You might have noticed that we released this fix in a major version (5.0
). This was done because to fix the issue, we had to fully deprecate the SimpleForm.file_methods
configuration. There are no other breaking changes in this release, so it should be easy to upgrade.
If you had changed the SimpleForm.file_methods
configuration to include other methods, please check whether they are from one of the supported Gems. If they are, you should be fine without them. If they are from another upload library, please open an issue asking for support and we’ll take a look into it.
In the meantime, you can explicitly say which type the input is:
<%= form.input :avatar, as: :file %>
Code language: HTML, XML (xml)
Can’t upgrade?
If you can’t upgrade for any reason but want to be protected from the security breach, you can also explicitly pass the input type – using as
– to your user-based forms. That would make Simple Form return early and not execute the user input on the object.
Acknowledgments
We want to thank Philipp Tessenow, who reported the issue with all the necessary details and was helpful throughout the steps of the fix until the release. Thanks!