Rails 4 Sanitizing User Input - sql

I am currently making an API using Ruby on Rails. I was just wondering in general if there are built in Rails methods or libraries/gems to sanitize Json and SQL or if Rails 4 does this by default? I am most worried about such cases where I have an SQL statement such as
User.where("users.first_name IS NOT NULL")
or something like
Event.where(:location => params[:location]).
Essentially, what should I watch out for in my SQL syntax and in incoming JSON requests?

By default, using the following will sanitize the str and make it safe from SQL injections:
User.where(name: str)
User.where('name ILIKE ?', str)
However, the following code (direct string interpolation then given to the where method) make it unsafe from SQL injections:
User.where("name = '#{str}'")
In your case, you can use ActiveRecord::Base.sanitize(your_string_from_user_input). It will use your DB adapter to escape/quote the relevant parts, preventing from SQL injections.
In a Model, you can directly access to the sanitize method (since you are in a context already inheriting from ActiveRecord::Base):
class User < ActiveRecord::Base
def self.search(string)
terms = string.split
searchable_columns = [:name, :username, :whatever]
query = terms.map do |term|
fields = searchable_columns.map |column|
" #{self.table_name}.#{column} LIKE '#{sanitize("%#{term}%")}'"
end
"(#{fields.join(' OR ')})"
end.join(' AND ')
where(query)
end
end
The above code will produce a SQL WHERE clause like the following:
# str is 'bob doe'
WHERE
(users.name LIKE 'bob' OR users.username LIKE 'bob' OR users.whatever LIKE 'bob')
AND
(users.name LIKE 'doe' OR users.username LIKE 'doe' OR users.whatever LIKE 'doe')

Rails will do it automatically if you format your queries properly
From the guides - don't do things like:
Project.where("name = '#{params[:name]}'")
Instead do
Project.where("name = ?", params[:name])

Related

SQL Interpolation from Ruby on Rails Tutorial

this is the code from Ruby on Rails Tutorial by MH:
def feed
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id)
end
Is this SQL safe? Because many people told me never use interpolation but use escaped code ever (with ? in this case). So is this code safe?
Yes, this is safe.
There is no interpolation, in fact: the whole query could be written as
Micropost.where("user_id IN (
SELECT followed_id FROM relationships
WHERE follower_id = :user_id)
OR user_id = :user_id", user_id: id)
but for the sake of clarity, the first query was extracted into it’s own variable.
Interpolation must be avoided when the interpolated string comes from the outside. This string is constructed by you, right here, hence there is no risk of SQL injection or like.
Examples
safe, id is determined:
id = 42
"SELECT * FROM users WHERE users.id = #{id}"
unsafe, params[:id] comes from the outside and might be dangerous:
"SELECT * FROM users WHERE users.id = #{params[:id]}"
This is safe because the string interpolation itself is not the issue. It only leads to a security vulnerability if you do not control the text that gets interpolated into the query.
In your example, the string following_ids that is inserted is not an unknown user input but a fixed SQL subquery. This cannot lead to a security issue.
But I agree that this is still not a good example and should probably be refactored to use scopes and Rails query syntax.

Is ActiveRecord's "order" method vulnerable to SQL injection?

I know it's not safe to use interpolated strings when calling .where.
e.g. this:
Client.where("orders_count = #{params[:orders]}")
should be rewritten as:
Client.where("orders_count = ?", params[:orders])
Is it safe to use interpolated strings when calling .order? If not, how should the following be rewritten?
Client.order("#{some_value_1}, #{some_value_2}")
Yes, ActiveRecord's “order” method is vulnerable to SQL injection.
No, it is not safe to use interpolated strings when calling .order.
The above answers to my question have been confirmed by Aaron Patterson, who pointed me to http://rails-sqli.org/#order . From that page:
Taking advantage of SQL injection in ORDER BY clauses is tricky, but a
CASE statement can be used to test other fields, switching the sort
column for true or false. While it can take many queries, an attacker
can determine the value of the field.
Therefore it's important to manually check anything going to order is safe; perhaps by using methods similar to #dmcnally's suggestions.
Thanks all.
Short answer is you need to sanitize your inputs.
If the strings you are planning to interpolate come from an untrusted source (e.g. web browser) then you need to first map them to trusted values. You could do this via a hash:
# Mappings from known values to SQL
order_mappings = {
'first_name_asc' => 'first_name ASC',
'first_name_desc' => 'first_name DESC',
'last_name_asc' => 'last_name ASC',
'last_name_desc' => 'last_name DESC',
}
# Ordering options passed in as an array from some source:
order_options = ['last_name_asc', 'first_name_asc']
# Map them to the correct SQL:
order = order_options.map{|o| order_mappings[o] }.compact.join(', ')
Client.order(order)
#Mike explanation is correct. #dmcnally workaround would work. I'm following in a slightly different path mentioned in [Railscast][1] http://railscasts.com/episodes/228-sortable-table-columns
In a nutshell, if you can construct a private method in the controller, in order to sanitize the user input:
Order by name of one your table columns:
private
def sort_column
Client.column_names.include?(params[:sort]) ? params[:sort] : "first_name"
end
Order by other criteria, then use the whitelist construct such as below:
def sort_direction
%w[asc desc].include?(params[:direction]) ? params[:direction] : "asc"
end
And your controller method should then look like this:
Client.all.order(sort_column + " " + sort_direction)
Just anther way to Rome. Hope this help.
Let's try this!
# app/models/concern/ext_active_record.rb
module ExtActiveRecord
extend ActiveSupport::Concern
included do
scope :sortable, -> (params) do
return unless params[:sort_by] && params[:sort_dir]
reorder("#{params[:sort_by]}" => "#{params[:sort_dir]}")
end
end
end
# app/models/user.rb
class User < ActiveRecord::Base
include ExtActiveRecord
# ....
end
# app/controllers/user_controller.rb
class UserController < ApplicationController
def index
#users = User.sortable(params).page(params[:page]).per(params[:per])
end
end
Client.order("#{some_value_1}, #{some_value_2}")
should be written as
order = sanitize_sql_array(['%s, %s', some_value_1, some_value_2])
Client.order(order)

How is the Arel#extract method used in rails?

I am trying to use the Arel#extract method. I have seen an example in a test case in test_extract.rb in the source but when I try to reproduce it in my app, I get undefined method.
table = Arel::Table.new :users
puts table[:created_at].extract('hour').to_sql
=> NoMethodError: undefined method `extract' for #<Arel::Attributes::Attribute:0x7..8>
I am using pg as the database.
Update:
My goal is to end up with this result in sql:
SELECT users.* FROM users WHERE EXTRACT(HOUR from users."created_at") = '1'
I would like to find all users that were created on the hour equal to one of any day. This works in sql but I am wondering how to create it in arel. Here is an example of how it's used in the arel test suite.
extract is node's method, you can cast it on any column(such as users[:id]), but not on Arel::Table instance.
So, to construct your query you need:
get Arel::Table instance users = Arel::Table.new(:users) or if you use ActiveRecord - users = User.arel_table
set SELECT statement on Arel::Table instance with project method: users = users.project(Arel.sql('*'))
set WHERE statement with where method: users.where(users[:created_at].extract(:hour).eq(1))
In one block:
query = User.arel_table.
project(Arel.sql('*')).
where(users[:created_at].extract(:hour).eq(1))
users = User.find_by_sql(query)
# => "SELECT * FROM \"users\" WHERE EXTRACT(HOUR FROM \"users\".\"created_at\") = 1"
I've had to perform an extract DOW on an instance of Arel::Nodes::NamedFunction, which does not expose the method #extract (as of Arel 6.0). I managed to achieve this by manually creating an instance of Arel::Nodes::Extract. Here is what worked for me, in case anyone have a similar issue:
Arel::Nodes::Extract.new(
Arel::Nodes::NamedFunction.new('some_function_name', [param1, param2, ...]),
:dow
)
You can use an Arel node directly with ActiveRecord's #where instead of building the full query through Arel as exemplified by Alexander Karmes's answer. So here is another way to perform the query required by the answer:
User.where(
Arel::Nodes::Extract.new(User.arel_table[:created_at], :hour).eq(1)
)
Which yields:
SELECT "users".* FROM "users" WHERE EXTRACT(HOUR FROM "users"."created_at") = 1
With the added benefit you can keep chaining other scopes defined in your User model.

Database-independent SQL String Concatenation in Rails

I want to do a database-side string concatenation in a Rails query, and do it in database-independent way.
SQL-92 specifies double-bar (||) as the concatenation operator. Unfortunately it looks like MS SQL Server doesn't support it; it uses + instead.
I'm guessing that Rails' SQL grammar abstraction has solved the db-specific operator problem already. If it does exist, how do I use it?
I had the same problem and never came up with anything that was built into Rails. So I wrote this little method.
# Symbols should be used for field names, everything else will be quoted as a string
def db_concat(*args)
adapter = configurations[RAILS_ENV]['adapter'].to_sym
args.map!{ |arg| arg.class==Symbol ? arg.to_s : "'#{arg}'" }
case adapter
when :mysql
"CONCAT(#{args.join(',')})"
when :sqlserver
args.join('+')
else
args.join('||')
end
end
I'm thinking somebody should really write some sort of SQL helper plugin that could automatically format simple SQL expressions based using the correct functions or operators for the current adapter. Maybe I'll write one myself.
It hasn't received much usage yet but I wrote the following code which seems to solve the problem. This monkey-patches the adapters to have a method to support it:
module ActiveRecord
module ConnectionAdapters
class AbstractAdapter
# Will return the given strings as a SQL concationation. By default
# uses the SQL-92 syntax:
#
# concat('foo', 'bar') -> "foo || bar"
def concat(*args)
args * " || "
end
end
class AbstractMysqlAdapter < AbstractAdapter
# Will return the given strings as a SQL concationation.
# Uses MySQL format:
#
# concat('foo', 'bar') -> "CONCAT(foo, bar)"
def concat(*args)
"CONCAT(#{args * ', '})"
end
end
class SQLServerAdapter < AbstractAdapter
# Will return the given strings as a SQL concationation.
# Uses MS-SQL format:
#
# concat('foo', 'bar') -> foo + bar
def concat(*args)
args * ' + '
end
end
end
end
With this you should be able to do the following in your code:
class User < ActiveRecord::Base
def self.find_by_name(name)
where("#{connection.concat('first_name', 'last_name')} = ?", name)
end
end
This outputs the following SQL query on a SQL-92 database (Oracle, SQLite, PostgreSQL):
SELECT * FROM users WHERE first_name || last_name = ?
For MySQL it outputs:
SELECT * FROM users WHERE CONCAT(first_name, last_name) = ?
For SQL Server it outputs
SELECT * FROM users WHERE first_name + last_name = ?
Obviously you could extend this concept to other database adapters.
If you want something Rails neutral, you're going to need to return the values you want concatenated and do that once the data has been delivered to rails (or do it in rails before you give it to the database).
It looks like Mysql uses CONCAT(), Postgres ||, Oracle CONCAT() or ||, T-SQL +.
Any rails abstraction of the same would have to take place at a point where you could just be doing concatenation using regular Ruby, or I've completely misunderstood the question.

Encapsulating SQL in a named_scope

I was wondering if there was a way to use "find_by_sql" within a named_scope. I'd like to treat custom sql as named_scope so I can chain it to my existing named_scopes. It would also be good for optimizing a sql snippet I use frequently.
While you can put any SQL you like in the conditions of a named scope, if you then call find_by_sql then the 'scopes' get thrown away.
Given:
class Item
# Anything you can put in an sql WHERE you can put here
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
end
This works (it just sticks the SQL string in there - if you have more than one they get joined with AND)
Item.mine.find :all
=> SELECT * FROM items WHERE ('user_id' = 887 and IS_A_NINJA() = 1)
However, this doesn't
Items.mine.find_by_sql 'select * from items limit 1'
=> select * from items limit 1
So the answer is "No". If you think about what has to happen behind the scenes then this makes a lot of sense. In order to build the SQL rails has to know how it fits together.
When you create normal queries, the select, joins, conditions, etc are all broken up into distinct pieces. Rails knows that it can add things to the conditions without affecting everything else (which is how with_scope and named_scope work).
With find_by_sql however, you just give rails a big string. It doesn't know what goes where, so it's not safe for it to go in and add the things it would need to add for the scopes to work.
This doesn't address exactly what you asked about, but you might investigate 'contruct_finder_sql'. It lets you can get the SQL of a named scope.
named_scope :mine, :conditions=>'user_id = 12345 and IS_A_NINJA() = 1'
named_scope :additional {
:condtions => mine.send(:construct_finder_sql,{}) + " additional = 'foo'"
}
sure why not
:named_scope :conditions => [ your sql ]