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
).
Share via: