A common way to download files in Ruby is using the open-uri
library and simply calling open(url)
. However, it has been pointed out that this passes input along to Kernel#open
, which is not safe to call with untrusted input (starting with a pipe spawns a subprocess, etc).
What is the best practice way to securely download a file in Ruby when the URL is constructed from potentially untrusted user input?
Firstly, consider why are you allowing this in the first place? Giving users the power to open arbitrary URLs on the server is an unusual (and "dangerous") thing to be doing in the first place!
For instance, even if you were to protect against process spawns, the user could still open arbitrary code from the internet - e.g. a virus.
It is most likely that any system allowing this will already trust its users. For example, perhaps the only user is yourself!!
...With that said, here is the source code for Kernel#open
when you have also used require 'open-uri'
:
alias open_uri_original_open open
def open(name, *rest, &block)
if name.respond_to?(:open)
name.open(*rest, &block)
elsif name.respond_to?(:to_str) &&
%r{\A[A-Za-z][A-Za-z0-9+\-\.]*://} =~ name &&
(uri = URI.parse(name)).respond_to?(:open)
uri.open(*rest, &block)
else
open_uri_original_open(name, *rest, &block)
end
end
So for the use case of only opening a URL, you can see that the implementation is to call: URI.parse(url).open
. Therefore, your "secure" code could be implemented as:
def open_url(url)
if url =~ URI.regexp
URI.parse(url).open
else
# Handle this somehow?
end
end
...But remember, as I said above, you really need to think twice before downloading arbitrary URLs! You should probably only being doing this if you already trust the user input; in which case my above code is likely unnecessary!