The Ruby on Rails framework provides a pretty good built-in filter for SQL injection if you use ActiveRecord methods such as find or find_by.
But that does not mean you can carelessly throw parameters to an ActiveRecord method as the methods that take an SQL fragment are still vulnerable to SQLi by default.

For example, I came across these lines in a production environment:

def some_controller_method  
  MyModel.all.order("#{sort_column} #{sort_direction}")
end

private

def sort_column  
  params[:sort] ? params[:sort] : 'created_at'
end  

As you can see the sort parameter is being interpolated in a query fragment without any kind of filtering. That means the query is vulnerable to SQLi.

Given that it is the order fragment we can do boolean-based blind SQL injection as described on the rails SQLi guide.

To test it we will submit the following as sort parameter:

( SELECT CASE WHEN (1=1) THEN something ELSE 1/( SELECT 0) END )

Which, if we check the logs results in the following query:

SELECT "my_models".* FROM "my_models" ORDER BY ( SELECT CASE WHEN (1=1) THEN latitude ELSE 1/( SELECT 0) END ) desc  

Now we could automatize the process with sqlmap or a script so the boolean condition is replaced with something more interesting in order to dump some database tables.

The counter-measure

The problem here is that sort is receiving a value that should not be allowed. We can do some filtering previously to returning the value by

  • Checking that the sort parameter is just one word long
  • Checking that the sort parameter is a model column
def sort_column  
  # Allow just a one-word lenght paramater
  if params[:sort] && params[:sort].match(/^\w+$/)
    params[:sort]
  else
    'created_at'
  end
end  
def sort_column  
  # Do not allow anything that it is not a model column
  if MyModel.column_names.include? params[:sort]
    params[:sort]
  else
    'created_at'
  end
end