What are the Rails methods that are vulnerable to SQL injection, and in what form?
For example, I know that where with a string argument is vulnerable:
Model.where("name = #{params[:name]}") # unsafe
But a parameterized string or hash is not:
Model.where("name = ?", params[:name]) # safe
Model.where(name: params[:name]) # safe
I'm mostly wondering about where, order, limit and joins, but would like to know about any other methods that might be attack vectors.
In Rails, where, order, limit and joins all have vulnerable forms. However, Rails limits the number of SQL operations performed to 1 so vulnerability is limited. An attacker cannot end a statement and execute a new arbitrary one.
Where
Where has one vulnerable form: string.
# string, unsafe
Model.where("name = '#{params[:name]}'")
# hash/parameterized string/array, safe
Model.where(name: params[:name])
Model.where("name = ?", params[:name])
Model.where(["name = ?", params[:name]])
Order
String form is vulnerable:
# unsafe
params[:order] = "1; --\n drop table users;\n --"
Model.order("#{params[:order]} ASC")
# safe
order_clause = sanitize(params[:order])
Model.order(order_clause)
Limit
Limit has no vulnerable forms, since Rails casts input to Integer beforehand.
Model.limit("1; -- \n SELECT password from users; -- ")
=> ArgumentError: invalid value for Integer(): "1; -- \n SELECT password from users; -- "
Joins
String form is vulnerable:
params[:table] = "WHERE false <> $1; --"
Model.where(:user_id => 1).joins(params[:table])
=> SELECT "models".* FROM "models" WHERE false <> $1 -- WHERE "models"."user_id" = $1 [["user_id", 1]]
Much more comprehensive information can be found at rails-sqli.org.
Generally: If you let the user input and save any text into your database, without escaping code, it could harm your system. Especially if these texts may contain tags/code snippets.
Related
Not sure how to do this so title may not be correct.
Each User has a field country of type String.
Given an array of user_id, country tuples for the query, find all the records that match. Each User must be found with it's own country.
For example, here is the array of tuples.
[1, 'us'],
[2, 'mexico'],
[3, 'us']
This would return User 1 if it exists and its country is 'us'.
It should also return User 2 if it exists and its country is 'mexico'.
The query should return all matching results.
Rails 4.2
class User < ApplicationRecord
def self.query_from_tuples(array_of_tuples)
array_of_tuples.inject(nil) do |scope, (id, country)|
if scope
scope.or(where(id: id, country: country))
else
where(id: id, country: country) # this handles the initial iteration
end
end
end
end
The resulting query is:
SELECT "users".* FROM "users"
WHERE (("users"."id" = $1 AND "users"."country" = $2 OR "users"."id" = $3 AND "users"."country" = $4) OR "users"."id" = $5 AND "users"."country" = $6)
LIMIT $7
You could also adapt kamakazis WHERE (columns) IN (values) query by:
class User < ApplicationRecord
def self.query_from_tuples_2(array_of_tuples)
# just a string of (?,?) SQL placeholders for the tuple values
placeholders = Array.new(array_of_tuples.length, '(?,?)').join(',')
# * is the splat operator and turns the tuples (flattened) into
# a list of arguments used to fill the placeholders
self.where("(id, country) IN (#{placeholders})", *array_of_tuples.flatten)
end
end
Which results in the following query which is a lot less verbose:
SELECT "users".* FROM "users"
WHERE ((id, country) IN ((1,'us'),(2,'mexico'),(3,'us'))) LIMIT $1
And can also perform much better if you have a compound index on [id, country].
I know this would work in pure SQL: e.g.
SELECT * FROM user
WHERE (id, country) IN ((1, 'us'), (2, 'mexico'), (3, 'us'))
Now I don't know how Rails would handle the bind parameter if it was a list of pairs (list of two elements each). Perhaps that would work.
You can construct a raw sql and use active record. Something like this:
def self.for_multiple_lp(arr=[])
# Handle case when arr is not in the expected format.
condition = arr.collect{|a| "(user_id = #{a[0]} AND country = #{a[1]})"}.join(" OR ")
where(condition)
end
Edit: Improved Solution
def self.for_multiple_lp(arr=[])
# Handle case when arr is not in the expected format.
condition = arr.collect{|a| "(user_id = ? AND country = ?)"}.join(" OR ")
where(condition, *(arr.flatten))
end
This should work.
do you know how to build a dynamic query avoiding sql injection
?
property = 'foo'
value = 'bar'
SomeObject.where("#{property} > ?", value)
# works but permit sql inj
SomeObject.where(":property > :value", property: property, value: value)
# create select * from some_object where 'foo' > 'bar'
# and the 'foo' I need without the quotes
SomeObject.where(
"#{SomeObject.connection.quote_column_name(property)} > :value",
value: value
)
UPDATE
Example 1 (trying to end the statement and inject a new one):
property = '; DROP TABLE users; --'
User.where("#{User.connection.quote_column_name(property)} > :value", value: 3)
# => SELECT "users".* FROM "users" WHERE ("; DROP TABLE users; --" > 3)
Example 2 (trying to end the column name quote):
property = '"; DELETE FROM users;--'
User.where("#{User.connection.quote_column_name(property)} > :value", value: 3)
# => SELECT "users".* FROM "users" WHERE ("""; DELETE FROM users;--" > 3)
Not sure of your use case but arel can help you with this like so
some_object_table = SomeObject.arel_table
SomeObject.where(some_object_table[property.intern].gt(value))
This will execute the query appropriately with all the escaping you have come to love with rails.
This works because arel is the underlying query assembler used by rails so ActiveRecord where clauses can understand Arel::Nodes without issue (its actually how they are assembled to begin with)
Also given the dynamic nature you may want to check that property is a valid column to avoid SQL level errors something like
raise AgrumentError unless some_object_table.engine.columns.map {|c| c.name.intern}.include?(property.intern)
# or
raise AgrumentError unless SomeObject.column_names.map(&:to_sym).include?(property.to_sym)
A simple but secure way would be to whitelist the allowed property names:
PROPERTIES = ["foo", "bar", "baz"].freeze
def find_greater_than(property, value)
raise "'#{property}' is not a valid property, only #{PROPERTIES.join(", ")} are allowed!" if !PROPERTIES.include?(property)
SomeObject.where("#{property} > ?", value)
end
You can (as #engineersmnky pointed out) dynamically check for available columns:
raise "Some Message" if SomeObject.column_names.include?(property)
but I don't like this aproach as having columns searchable should be a decission, not automated.
Another aproach is to use the sanitizing provided by Rails.
def find_greater_than(property, value)
sanitized_property = ActiveRecord::Base.connection.quote_column_name(property)
SomeObject.where("#{sanitized_property} > ?", value)
end
The quoting logic is implemented by the DB specific connection adapters.
In my case, I ended by using
ActiveRecord::Base.connection.quote_table_name function to sanitize the column name before queries.
property = 'foo'
value = 'bar'
sanitized_property = ActiveRecord::Base.connection.quote_table_name(property)
SomeObject.where("#{sanitized_property} > ?", value)
that function can handle table and column name definition.
property = 'table.column'
will produce
where `table`.`column` > `bar`
I've been puzzled for a while over the difference between using a question mark, e.g.
Foo.find(:all, :conditions => ['bar IN (?)', #dangerous])
and using sprintf style field types, e.g.
Bar.find(:all, :conditions => ['qux IN (%s)', #dangerous])
in sanitizing inputs. Is there any security advantage whatsoever, if you know you're looking for a number - like an ID - and not a string, in using %d over ?, or are you just asking for a Big Nasty Error when a string comes along instead?
Does this change at all with the newer .where syntax in Rails 3 and 4?
%s is intended for strings. The main difference is that %s doesn't add quotes. From ActiveRecord::QueryMethods.where:
Lastly, you can use sprintf-style % escapes in the template. This
works slightly differently than the previous methods; you are
responsible for ensuring that the values in the template are properly
quoted. The values are passed to the connector for quoting, but the
caller is responsible for ensuring they are enclosed in quotes in the
resulting SQL. After quoting, the values are inserted using the same
escapes as the Ruby core method Kernel::sprintf.
Examples:
User.where(["name = ? and email = ?", "Joe", "joe#example.com"])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe#example.com';
User.where(["name = '%s' and email = '%s'", "Joe", "joe#example.com"])
# SELECT * FROM users WHERE name = 'Joe' AND email = 'joe#example.com';
Update:
You are passing an array. %s seems to calls .to_s on the argument so this might not works as expected:
User.where("name IN (%s)", ["foo", "bar"])
# SELECT * FROM users WHERE (name IN ([\"foo\", \"bar\"]))
User.where("name IN (?)", ["foo", "bar"])
# SELECT * FROM users WHERE (name IN ('foo','bar'))
For simple queries you can use the hash notation:
User.where(name: ["foo", "bar"])
# SELECT * FROM users WHERE name IN ('foo', 'bar')
As far as I can tell, %s simply inserts into your query whatever #dangerous.to_s happens to be, and you are responsible for it.
For example, if #dangerous is an array of integers, then you will get an SQL error:
#dangerous = [1,2,3]
User.where("id IN (%s)", #dangerous)
will result in the following incorrect syntax:
SELECT `users`.* FROM `users` WHERE (id IN ([1, 2, 3]))
whereas:
User.where("id IN (?)", #dangerous)
produces the correct query:
SELECT `users`.* FROM `users` WHERE (id IN (1,2,3))
Thus, is seems to me that unless you know very, very well what you are doing, you should let the ? operator do its job, ESPECIALLY if you do not trust the content of #dangerous as safe.
If i use expression Model.find(1) then rails executes it as prepared statement: SELECT "models".* FROM "models" WHERE "models"."id" = $1 LIMIT 1 [["id", 1]]
But when I use Model.where("id = ?", 1) it executes without prepared statement: SELECT "models".* FROM "models" WHERE (id = 1)
How to force rails to use prepared statement in this case too?
i'm not sure you can.
But
Model.where(:id => 1)
Should generate a prepared statement. Fragment string works differently, so you can generate exactly what you need, in custom cases.
Edit :
Try this, i'm not sure it works, i can' test for now, but it looks like what you need :
Client.where("created_at >= :start_date AND created_at <= :end_date",
{:start_date => params[:start_date], :end_date => params[:end_date]})
More here: http://guides.rubyonrails.org/active_record_querying.html#conditions
In the placeholder section. Moreover date range works in the array format :
:date => date_begenning..date_end
Edit 2 : indeed you can, but i seems where don't support building this by hand.
You might build your prepared queries by hand :
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/DatabaseStatements.html#method-i-exec_query
exec_query(sql, name = 'SQL', binds = [])
There is also a bind method on your relation.
But both binds need that the value have a name method, they will crash if you give a fixnum, a hash, ect. I hadn't found any doc explaining how this works.
Bu for the where clause, as long it is of Class String, active ecord apply a .to_sql on it, so you don't get your prepared statement.
I need to run sql query like
sql = 'SELECT * FROM users WHERE id != ' + self.id.to_s + ' AND id NOT IN (SELECT artner_id FROM encounters WHERE user_id = ' + self.id.to_s + ')'
sql += ' AND id NOT IN (SELECT user_id FROM encounters WHERE partner_id = ' + self.id.to_s + ' AND predisposition = ' + Encounter::Negative.to_s + ')'
sql += ' AND cfg_sex = ' + self.sex.to_s + ' AND cfg_country = ' + self.country.to_s + ' AND cfg_city = ' + self.city.to_s
sql += ' ORDER BY rand() LIMIT 1'
It can be executed by AR.find_by_sql, but the code before is bad readable.
Are there any query builder, which can build that query?
For example, Kohana (it is PHP framework, I am php developer, but I want to change that kid-language to ruby/rails) have a query builder, which works like this:
$sql = DB::select('*')->from('users');
$sql->where('id', 'NOT_IN', DB::expr('SELECT partner_id FROM encounters WHERE user_id = '.$user->id));
$sql->where('id', 'NOT_IN', DB::expr('SELECT user_id FROM encounters WHERE partner_id = '.$user->id.' AND predisposition = '.Encounter::Negative));
....
etc
...
Query which was builded with query builder like a Kohana query builder is more readable and understandable.
Are there any gem to solve this problem?
You need the squeel gem. It extends AR with blocks and makes very complicated queries with ease.
Just few features:
# not_in == cool! )
Product.where{id.not_in LineItem.select{product_id}}
# SELECT "products".* FROM "products" WHERE "products"."id" NOT IN
# (SELECT "line_items"."product_id" FROM "line_items" )
# outer joins on pure Ruby:
LineItem.joins{product.outer}
# LineItem Load (0.0ms) SELECT "line_items".* FROM "line_items"
# LEFT OUTER JOIN "products" ON "products"."id" = "line_items"."product_id"
# calcs, aliasing:
Product.select{[avg(price).as(middle)]}
# SELECT avg("products"."price") AS middle FROM "products"
# comparison
Product.where{id != 100500}
Product.where{price<10}
# logical OR
Product.where{(price<10) | (title.like '%rails%')}
# SELECT "products".* FROM "products" WHERE (("products"."price" < 10 OR
# "products"."title" LIKE '%rails%'))
# xxx_any feature (also available xxx_all)
Product.where{title.like_any %w[%ruby% %rails%]}
# SELECT "products".* FROM "products" WHERE (("products"."title" LIKE '%ruby%' OR
# "products"."title" LIKE '%rails%'))
Note the using blocks: {...} here aren't hashes. Also note the absence of symbols.
If you decide to pick it, read the section that starts with "This carries with it an important implication"
There's a ruby library that utilizes relational algebra. It is called ARel. If you are using Rails 3.x, then you already have.
ids = Partner.where(user_id: self.id).pluck(:partner_id) << self.id
users = User.where("id NOT IN #{ ids.join(',') }")
Here's the same query cast into rails AREL terms. It's not pretty yet -- it's a complicated query in general.
User.where("id = ? AND "
"id NOT IN (SELECT artner_id FROM encounters WHERE user_id = ?) AND " +
"id NOT IN (SELECT user_id FROM encounters WHERE partner_id = ? AND predisposition = ? ) AND " +
"cfg_sex = ? AND cfg_country = ? AND cfg_city = ?)",
self.id, self.id, self.id, Encounter::Negative,
self.sex, self.country, self.city).order(" rand() ").limit(1)
(I've not tested this, so it's possible there could be typo's in it.)
I'd recommend a couple things:
When you have complex where clauses they can be chained together and AREL will put them back together generally pretty well. This allows you to use scopes in your model classes and chain them together.
For example, you could do this:
class User < ActiveRecord::Base
def self.in_city_state_country(city, state, country)
where("cfg_sex = ? AND cfg_country = ? AND cfg_city = ?", city, state, country)
end
def self.is_of_sex(sex)
where("cfg_sex = ?", sex)
end
end
Then you could rewrite these portions of the query this way:
User.is_of_sex(user.sex).in_city_state_country(user.city, user.state, user.country)
and so on.
Breaking the queries down into smaller parts also makes it easier to test specific pieces of it with your rspecs. It results in more modular, maintainable code.
For more details, check out the Rails Guide - Active Record Query Interface