The nVisium Blog

Rails Dynamic Render to RCE (CVE-2016-0752)

Tl;dr: If your application uses dynamic render paths (eg: render params[:id]) then you are vulnerable to remote-code execution via local file inclusion. Update to the latest version of Rails, or refactor your controllers.

In this blog post we will be demonstrating the exploitation of a flaw in the Ruby on Rails framework that allows attackers to remotely execute code in certain circumstances.

Rails controllers are designed to implicitly render view files based on the method being invoked. For example, when evaluating a controller's show method the framework will fall back to implicitly rendering the show.html.erb file if there are no other explicit render statements.

In many cases, however, developers will decide to explicitly render different content based on format such as text, JSON, XML or an entirely different view file. A view file, in this context, would be a templating language file such as ERB, HAML, and so on. There are several methods which can be used to influence the view content. For our purposes, we will focus on the render method. The Rails documentation highlights several ways to invoke the render method, including the ability to explicitly specify a path to render using the file: option.

If you have read the documentation around this method and find yourself a bit unsure about the desired functionality, you’re not alone. In fact, let’s consider a snippet of code:

def show
  render params[:template]
end

At first glance this code seems pretty simple, and one would guess that the desired purpose of the specified controller action was to render the view template, which is specified via the template parameter. It is not clear, though, where Rails will look to find the specified template. Does it look in the views directory, does it look in the application root, or does it look elsewhere? Is it expecting a template name, a filename with a specific extension, or a full file path? There are many unanswered questions that can only be resolved by looking at the implementation details.

Explanation

The render mechanism seems to be a prime example of a single function trying to accomplish too much, and this is where the problem lies.

Let us assume that the expected behavior for the render mechanism is to attempt to render the file in app/views/user/#{params[:template]} – this seems like a plausible thought. Providing the template parameter with a value of dashboard will attempt to load the template in app/views/user/dashboard.{ext}, where .ext is any allowable extension (such as .html, .haml, .html.erb, etc.)

Consider now a user that enters the following value for a template parameter: ../admin/dashboard. What is the expected result? It may be hard to know for sure, but when we try this you can see that the application throws a missing template error.

Upon analyzing the error, it appears that the application is attempting to render the view by searching for it in several paths, including RAILS_ROOT/app/views, RAILS_ROOT and the file system root. This is quite worrisome, because why the heck would we ever want to render a file from the root of the file system?

Our hacker instinct kicks in and we decide to pass in /etc/passwd as the template parameter, and confirm that we are actually able to read our passwd file. This is a huge concern.

If we can read the passwd file we can probably read the application’s source code and configuration values, such as the config/initializers/secrettoken.rb_ file.

In case you have forgotten why this is occurring, it is because we chose to use dynamic render paths within our application:

def show
  render params[:template]
end

Such a simple code snippet has provided the ability for an attacker to read our source code and application configuration values. Unfortunately, that is not the worst part.

As Jeff Jarmoc describes in his paper “The Anatomy of a Rails Vulnerability – CVE-2014-0130: From Directory Traversal to Shell,” we can leverage this vulnerability to gain a shell on our Rails application. Jeff’s paper describes a similar flaw in Rail’s implicit render mechanism that allows for directory traversal, or more accurately, local file inclusion, in certain versions of Rails. In this blog post we are addressing the concern related to explicit rendering, which is a developer-introduced vulnerability.

Before we dive into the details I want to point out that the category of vulnerability we are addressing is file inclusion and not directory traversal. The differentiating factor is that we are loading, interpreting, and executing the file as code (ERB). Traditionally, directory traversal vulnerabilities return non-executable content, such as a CSV file. So, in essence, not only can we read the application’s source code, and other system readable files, but we can also execute Ruby code. Because we can execute Ruby code, we can also execute system-level commands on behalf of the web server.

A key factor in transitioning this attack from file inclusion to shell is a technique known as log file tainting. Rails is designed to log request information, including parameters, for each request within the environment’s log file (such as development.log). The log file, which is otherwise plaintext, can be tainted with Ruby code. This can be accomplished by making a request to the web application using valid Ruby code, contained within a parameter.

In the following example we make a legitimate request to the web application but pass along the fake parameter with the following URL-encoded value <%= `ls` %>.

Upon review of the log file, we can see the associated entry, with parameters represented as a hash with keys URL-decoded. This is valid Ruby code and would execute if this file were rendered within our application.

We can then leverage the file inclusion vulnerability to attempt to load the log file, executing the Ruby code.

When the log file is returned we see the parameters hash. Notice that the fake parameter’s value which, previously contained our payload, has been replaced with the output of the ls command. At this point we can execute any system-level command with which the web-server has privileges to run.

Mitigation

At this point you’re probably wondering how we can mitigate the vulnerability. Most importantly, apply the patch for your specific version of Rails.

If you haven't installed the patch, consider not attempting to render non-acceptable files. This can be accomplished by creating a hash of valid filenames that can be rendered in the given context, and ensure that the user parameter is included in the hash. This will need to be accomplished for each instance of dynamic render paths within the application.

def show
  template = params[:id]

  valid_templates = {
    "dashboard" => "dashboard",
    "profile"   => "profile",
    "deals"   => "deals"
  }

  if valid_templates.include?(template)
    render " #{valid_templates[template]}"
  else
    # throw exception or 404
  end
end

Another similar solution would be to verify that the given file exists within the context of a specified directory.

def show
  template = params[:id]
  d = Dir["myfolder/*.erb"]

  if d.include?("myfolder/#{template}.erb")
    render "myfolder/#{template}"
  else
    # throw exception or 404
  end
end

Leverage Brakeman, a Rails static-analysis tool to scan the application. Brakeman will report instances of dynamic render paths, amongst other things. This will allow you to determine which controllers within your application are affected.

Timeline

  • Vulnerability was reported on Feburary 1st, 2015.
  • The Rails team agreed to fix the vulnerability on Feburary 10th, 2015.
  • Patch was privately verified on July 13th, 2015 -- Over 5 months from the original report date.
  • Patch was released on January 25th, 2016 with CVE identifier CVE-2016-0752 via a security advisory -- Over 6 months from verification of the patch and almost a year from the original report date.

Conclusion

The Rails render mechanism is a mysterious function that is difficult to understand without digging into the implementation details, or actively trying to exploit it. Unfortunately for us, the documentation is not very helpful either.

Similar to CVE-2014-0130, using dynamic render paths can lead to directory traversal and code execution. I have seen this developer-introduced vulnerability several times while performing assessments as well as in several popular Rails open-source projects.

If you have not yet read Jeff Jarmoc’s paper, I recommend doing so. He goes into great depth in relation to the exploitation of CVE-2014-0130 as well as risk evaluation. A lot of this content is similar because these are, in fact, very similar vulnerabilities.

I have written a proof of concept Metasploit module that will attempt to detect and exploit this vulnerability in web applications. You can find the module here: https://gist.github.com/forced-request/5158759a6418e6376afb