Ruby’s OpenURI is an easy-to-use wrapper for net/http, net/https and net/ftp. As far as I know it’s the most popular way to read URL content, make a GET request or download a file.

require "open-uri" internally patches Kernel.open leaving you one step away from remote code execution and reading local files! Here are some examples:

open(params[:url]) is remote code execution for url=|ls Didn’t you know if it starts with a pipe Ruby executes it?! Ouch.

open(params[:url]) if params[:url] =~ /^https:// is also code execution for url=|touch n;\nhttps://url.com (broken Regexp, use \A\z).

open(URI(params[:url])) reads any file on the system. url=/etc/passwd is a valid URL but open-uri calls original Kernel.open instead, because it doesn’t start with https://

On top of that open-uri doesn’t limit number of redirects (raise "HTTP redirection loop: #{uri}" if uri_set.include? uri.to_s is not enough) and allows http: -> ftp: redirects with (/\A(?:http|ftp)\z/i =~ uri1.scheme && /\A(?:http|ftp)\z/i =~ uri2.scheme). Following redirects by default is also not a great idea (Location: https://127.0.0.1/send_bitcoins and other SSRFs)

open-uri library is a great example of really bad security design - it patches a critical system method to allow reading URLs (most likely from user input) and we don’t tell developers to be extremely careful. I’m sure in the next few weeks we will hear about severe bugs in popular ruby gems, if you guys will join my research.

One more example: open(params[:url]) if URI(params[:url]).scheme == 'http'. Looks good, but if you manage to create a folder called “http:”, the attacker can read local files with http:/../../../../../etc/passwd (hello, CarrierWave!). Yes, unlikely the victim has such folder but it is a perfect showcase why URL validation is hard and why open-uri is a disaster for Ruby community.

I recommend Ruby core developers to deprecate it and create openURI method instead.

Feb 28, 2015 • Egor Homakov (@homakov)