Ruby Unsafe Reflection Vulnerabilities

One of the interesting properties of Ruby is the fact that everything is an object. The manipulation of objects at runtime is what makes Ruby so flexible and interesting as a language. At its heart, this is the idea behind reflection. Reflection is a tool to allow a program to examine and modify its own behavior at runtime, granting programmers the ability to simpilify certain constructs (e.g. framework development, dependency resolution).

One of the features Ruby on Rails adds to Ruby is the String#constantize function. It provides a solution to the common problem of converting a String into a Class, then allowing the programmer to call methods or instantiate objects based on the contents of the String. The Rails documentation provides the following description and code examples:

String#constantize tries to find a constant with the name specified in the argument string.

		'Module'.constantize     # => Module'Test::Unit'.constantize # => Test::Unit	

The name is assumed to be the one of a top-level constant, no matter whether it starts with “::” or not. No lexical context is taken into account:

		C = 'outside'module M  C = 'inside'  C               # => 'inside'  'C'.constantize # => 'outside', same as ::Cend	

NameError is raised when the name is not in CamelCase or the constant is unknown.

Rails also provides a “safe” version of the function with String#safe_constantize. Safe, in this context, means the function does not raise an exception.

Recently, I was manually reviewing the source code of a Ruby on Rails application, and I discovered the use of this constantize function sprinkled throughout the codebase. As described above, the function is used to convert user input to a constant within the Ruby object space. This constant may then be used to call functions or instantiate objects. A generic example is shown below:

		class FooBarController < ApplicationController  # e.g. GET /foobar/13?klass=Baz::Small  def show    klass = params[:klass].constantize    klass.find(params[:id])  end  # e.g. POST /foobar?klass=Baz::Medium&name=bingo  def create    klass = params[:klass].constantize    klass.new(params[:name])  end  # e.g. PUT /foobar/13?klass=Baz::Large&name=bingo  def update    klass = params[:klass].constantize    baz = klass.find(params[:id])    baz.name = params[:name]    baz.save  endend	

For those unfamiliar with Ruby, the general concept of the above code can be boiled down to three user actions: get, create, and update. There is some class Baz with subclasses Small, Medium, and Large that must be accessible to the user. The user can query any of these subclasses and update the name attribute of their Baz object.

Unfortunately, this presents a massive security vulnerability. Reflection is a very dangerous beast, especially if used in conjunction with user input. Reflection is used to modifying the nature of a program at runtime and should not be used with Strings from untrusted sources. To demonstrate the danger of using constantize, imagine the following scenario.

A malicious attacker sifts through the Ruby standard library for any objects that execute commands after calling #new or #find. The Logger object fits these characteristics, allowing command execution based on the input parameter to Logger#new. The attacker then makes the following request to the server to test for code execution:

		POST /foobar HTTP/1.1Host: reflection.local	

		klass=Logger&name=|cat /etc/passwd	

Alternatively, the attacker can cause a denial of service by passing improper parameters to an object on instantiation. While creating content for this post, I discovered a few objects within the Ruby standard library that fit this characteristic. For example, at the time of this writing, the class OpenSSL::ASN1::Constructive contains a bug that causes a segmentation fault when the object is improperly constructed. An example request is shown below:

		POST /foobar HTTP/1.1Host: reflection.local	

		ng-xml --klass=OpenSSL::ASN1::Constructive&name=goodbye	

This request will not throw an exception within Ruby. Instead, this request will completely crash the server process yielding operating system stack trace. You can play along at home with the following:

		#!/usr/bin/env rubyrequire 'openssl'OpenSSL::ASN1::Primitive.new("hello")OpenSSL::ASN1::Constructive.new("world")	

Please consider the above when evaluating the use of reflection within web applications. Reflection, while a powerful tool, can be incredible harmful when used in improper contexts. In Ruby, there are a number of helpful ways to achieve reflection that all can lead to remote code execution or denial of service when called with user input (e.g. instance_eval, send, public_send, constantize).

icon-praetorian-

See Praetorian in Action

Request a 30-day free trial of our Managed Continuous Threat Exposure Management solution.

About the Authors

Anthony Weems

Anthony is a Principal Security Engineer at Praetorian.

Catch the Latest

Catch our latest exploits, news, articles, and events.

Ready to Discuss Your Next Continuous Threat Exposure Management Initiative?

Praetorian’s Offense Security Experts are Ready to Answer Your Questions