Incorrect Access Control in Simple Form (CVE-2019-16676)

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 %>

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: passwordtime_zonecountryemailphone 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

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 – avatarattachmentprofile_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")

The officially supported Gems are:

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 %>

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!

Comments are closed.