rubyhttpsecurity

Secure method to download files in Ruby


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?


Solution

  • 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!