Constantize with Care

The String#constantize method is a feature that makes rails fun to code with. This method converts a string to the constant that the string contains (or throws a NameError if there is no such constant). It makes it easy to store class-types in databases as strings and to code controllers that work with classes of the same duck type.
Most people know that eval on user data is dangerous, but noboddy seems to care about constantize. Beware, it is also dangerous and you should constantize with care!

If you want to turn a string into the corresponding class, you could just use eval:

  irb(main):001:0> s="Float"
  => "Float"
  irb(main):002:0> klass= eval(s)
  => Float

But everybody knows that this is dangerous since you can embed arbitrary ruby-code into the string which might do nasty things to your system. Rails defines the String#constantize method which is less dangerous. But it is not save by any means. Consider the following controller:

  def vulnerable_basic_data_new
     klass=params[:class]
     obj=klass.constantize.new(params[:form])
     #...
  end

The class parameter is supposed to be Guest, User or Admin. The posted form is supposed to contain a hash of data that initializes the common attributes of them. Using routes we could call http://myapp/vulnerable_basic_data_new/User/. That’s nice. A google search reveals that quite a lot of people write code like above .

But wait! What happens if we open http://myapp/vulnerable_basic_data_new/AnyClass/?form=somestring? Then the application will execute AnyClass.new "somestring" (if AnyClass exists). We just injected a class into the application that the programmer didn’t expect. A lazy programmer might argue that an attacker cannot control the method that is called, just the class. It is true, that the usual methods new and find don’t sound too dangerous. But what if we called create? And don’t forget that we can call any class that is installed on the system. Thanks to rails autoloading capabilities. So if you accidently installed MiniMagick which happens to have security issue right now in version 1.2.3, we can exploit it:
http://myapp/vulnerable_basic_data_new/MiniMagic::Image/?form=-%20%7C%20xclock.

This will execute MiniMagic::Image.new("- | xclock") which will launch a nice xclock on your server. We could have done worse…. Ooooops!

So we should constantize with care. For example, we could use the following method:

class String
  def constantize_with_care(list_of_klasses=[])
    list_of_klasses.each do |klass|
      return self.constantize if self == klass.to_s
    end
    raise "I'm not allowed to constantize #{self}!"
  end
end

And rewrite above controller code to:

  def vulnerable_basic_data_new
     klass=params[:class]
     obj=klass.constantize_with_care([Guest,User,Admin]).new(params[:form])
     #...
  end

There is a even more sopphisticated constantize_with_care plugin that you can download to use in your own projects.

P.S. In the above google search, I found a single person (Michael Schuerig) warning that using constantize carelessly is dangerous and a single project that tries to protect against class injection. Their safe_to_instantiate? makes me think about checking for class relationships in constantize_with_care too.

Update:
I have to apologize to Lisa Seelye. She constantizes params[:controller] which should be sanitized by the routing to contain only very few, save possibilites. At least as long as she does not create a MiniMagickController.

Update: I also have some notes on Constantize in Python.

Update: Gabriel Quadros wrote a nice post about Exploiting Unsafe Reflection in Ruby/Rails Applications.