Rails routing

Ruby on Rails 4.1.1 was released yesterday. It fixes a directory traversal attack when using wildcard segments in routes with implicit rendering (CVE-2014-0130). This blog post explains how we found the issue and learned about a surprising feature in Ruby’s Dir[] in the process.

We have the Flowdock API documentation source files in a separate, public GitHub repository. This allows anyone to contribute and report issues, or ask questions in public. We serve the documentation in Rails by rendering markdown as HTML and injecting the generated HTML files as views. To avoid adding a new route every time we add a new page, our route file had the following rule:

get '/api/*action', controller: 'docs'

In this setup the controller is only responsible for setting up the layout.

Given that there wasn’t any code of our own involved, a bug report from Jitendra Jaiswal surprised us. A request such as /api/%5C../%5C../%5C../Gemfile exposed files outside of Rails’ view paths. '%5C' turns into '\' after decoding. Using Rack::Protection didn’t help as it only rejects '/../' segments in the request path.

We deployed a fix to the issue within hours of noticing it. By checking our logs, we were able to verify that the vulnerability had never been exploited.

Being convinced that there is something fishy in Rails, we investigated further. Some code was removing backslashes. We eventually found that Rails’ ViewResolver used the following code to find candidate templates to render:

template_paths = Dir[query]

This looks innocent enough, but Dir[] is an alias for Dir.glob, which has two arguments: pattern and flags. One of the possible flags is File::FNM_NOESCAPE, which “makes ‘\’ ordinary”. In other words, by default, it assumes that backslash escapes special characters such as ‘*’ and ‘?’.

Further investigation revealed that the issue was not only limited to backslashes. Even a request to '/api/../../../Gemfile' exposed information when no extra measures were in place. We worked together with the Rails security team to fix this. Rails 4.1.1 removes support for path hierarchies in implicit rendering.

To achieve similar functionality, you need to implement path sanitization. The workaround suggested in CVE report is:

def display
  if !params[:template_path].index('.')
    render file: params[:template_path]
  end
end

This works unless '.' is a valid character in the directory path. Currently, backslashes are still treated as special characters in the view resolver. To enable dots so that an escaped ‘\’ cannot circumvent the protection, strip out backslashes with gsub.

def display
  if !params[:template_path].gsub(/\\/, '').index('../')
    render file: params[:template_path]
  end
end

The combined use of these two useful features had a surprising security implication. The Rails core team decided to exclude sub-directory views from implicit rendering. This means moving the responsibility to the application developer: you need to be aware that Rails uses Dir[] to find view files and that it ignores backslashes. Thank you to Jitendra Jaiswal for finding the issue in Flowdock and reporting it to us in accordance with our security response guidelines. Again, we’re happy to report that no information had leaked due to this vulnerability.