Search code examples
ruby-on-railsruby-on-rails-4refile

Refile Gem: Ignore exception when remote_image_url is invalid or doesn't exist


We are using refile to allow users to upload images to our S3 back end. In addition, we allow users the option of entering a URL to any image on the internet (through the remote_image_url property.)

This works fine, as long as the URL entered is pointing to an actual file. However, in the case where there was a mistake in the URL, or some nonsensical input was given, Refile will throw the following exception:

Errno::ENOENT (No such file or directory @ rb_sysopen - thiswillnotwork):
  app/controllers/my/deals_controller.rb:17:in `create'
  appsignal (0.11.2) lib/appsignal/rack/listener.rb:13:in `call'

Is there an option to ignore the cases where the URL entered is invalid (akin to how the validate_download option in CarrierWave works) and, ideally, use our fall back image instead?

We have tried mounting the attacher with the raise_errors option set to false, but with the same results.

Our project uses Rails 4.2.0 and Refile 0.5.3.

Edit:

I have confirmed that this exception is a lower level SystemCallError, coming from Kernel.open, and this exception type is not being rescued by Refile:

rescue OpenURI::HTTPError, RuntimeError => error
  raise if error.is_a?(RuntimeError) and error.message !~ /redirection loop/
  @errors = [:download_failed]
  raise if @raise_errors
end

I am working on a pull request to refile to fix this.

Edit 2:

While working on this, we discovered a major security issue in Refile, enabling a potential attacker to use remote code execution.

The Refile gem has a feature where a URL will be supplied and the remote file will be uploaded. This can be done by adding a field like remote_image_url in a form, where image is the name of the attachment. This feature was using open-uri to make this HTTP request without validating the passed URI. An attacker can craft a URI which executes arbitrary shell commands on the host machine.

If you are using Refile versions 0.5.0 - 0.5.3, please upgrade to the latest version. Upgrading will also solve the issue above.


Solution

  • After talking to the maintainer of Refile, this will be fixed in the next iteration. For now, we've created the following, dead simple Ruby class as a work around.

    # app/services/remote_url.rb
    class RemoteUrl
      def initialize(url)
        @url = url
      end
    
      def valid?
        URI.parse(@url).kind_of?(URI::HTTP)
      rescue URI::InvalidURIError
        false
      end
    end
    

    and I use it in my controller like so:

    # app/controllers/model_controller.rb
    def model_params
      sanitize_remote_url!
      params.require(:model).permit(:description, ..., :image, :remote_image_url)
    end
    
    def sanitize_remote_url!
      params[:model].delete(:remote_image_url) unless RemoteUrl.new(params[:model][:remote_image_url]).valid?
    end
    

    It is quite a primitive approach, but considering that it will be patched in the next Gem version, I consider this good enough for now.

    Disclaimer:

    Although this offsets some of the vulnerability mentioned in edit 2 of the original question, an attacker will still be able to do remote code execution, if they can create a folder named http: on the file system, as a string like http:/../etc/passwd will pass the URL validation, but still read a local file through Kernel.open.

    The Gem has now been patched (0.5.4), and an upgrade is the correct solution.