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, whereimage
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.
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.