I'm resolving all the SQL Injections in a system and I've found something that I don't know how to treat.
Can somebody help me?
Here is my method
def get_structure()
#build query
sql = %(
SELECT pc.id AS "product_id", pc.code AS "code", pc.description AS "description", pc.family AS "family",
p.code AS "father_code", p.description AS "father_description",
p.family AS "father_family"
FROM products pc
LEFT JOIN imported_structures imp ON pc.id = imp.product_id
LEFT JOIN products p ON imp.product_father_id = p.id
WHERE pc.enable = true AND p.enable = true
)
#verify if there is any filter
if !params[:code].blank?
sql = sql + " AND UPPER(pc.code) LIKE '%#{params[:code].upcase}%'"
end
#many other parameters like the one above
#execute query
str = ProductStructure.find_by_sql(sql)
end
Thank you!
You could use Arel which will escape for you, and is the underlying query builder for ActiveRecord/Rails. eg.
products = Arel::Table.new("products")
products2 = Arel::Table.new("products", as: 'p')
imported_structs = Arel::Table.new("imported_structures")
query = products.project(
products[:id].as('product_id'),
products[:code],
products[:description],
products[:family],
products2[:code].as('father_code'),
products2[:description].as('father_description'),
products2[:family].as('father_family')).
join(imported_structs,Arel::Nodes::OuterJoin).
on(imported_structs[:product_id].eq(products[:id])).
join(products2,Arel::Nodes::OuterJoin).
on(products2[:id].eq(imported_structs[:product_father_id])).
where(products[:enable].eq(true).and(products2[:enable].eq(true)))
if !params[:code].blank?
query.where(
Arel::Nodes::NamedFunction.new('UPPER',[products[:code]])
.matches("%#{params[:code].to_s.upcase}%")
)
end
SQL result: (with params[:code] = "' OR 1=1 --test")
SELECT
[products].[id] AS product_id,
[products].[code],
[products].[description],
[products].[family],
[p].[code] AS father_code,
[p].[description] AS father_description,
[p].[family] AS father_family
FROM
[products]
LEFT OUTER JOIN [imported_structures] ON [imported_structures].[product_id] = [products].[id]
LEFT OUTER JOIN [products] [p] ON [p].[id] = [imported_structures].[product_father_id]
WHERE
[products].[enable] = true AND
[p].[enable] = true AND
UPPER([products].[code]) LIKE N'%'' OR 1=1 --test%'
To use
ProductStructure.find_by_sql(query.to_sql)
I prefer Arel, when available, over String queries because:
it supports escaping
it leverages your existing connection adapter for sytnax (so it is portable if you change databases)
it is built in code so statement order does not matter
it is far more dynamic and maintainable
it is natively supported by ActiveRecord
you can build any complex query you can possibly imagine (including complex joins, CTEs, etc.)
it is still very readable
You need to turn that into a placeholder value (?) and add the data as a separate argument. find_by_sql can take an array:
def get_structure
#build query
sql = %(SELECT...)
query = [ sql ]
if !params[:code].blank?
sql << " AND UPPER(pc.code) LIKE ?"
query << "%#{params[:code].upcase}%"
end
str = ProductStructure.find_by_sql(query)
end
Note, use << on String in preference to += when you can as it avoids making a copy.
Related
Using Rails 5. Is this safe, or could this query be SQL injected?
(There's a form for selecting the region_ids on my site)
results = results.joins(:regionmemberships).where("regionmemberships.region_id = ? OR regionmemberships.region_id = ?", 0, 2) if region_id.present?
I've read that results.where('regionmemberships.region_id = ?', region_id)
.. is safe. But is the first statement, when using OR also safe?
Or is there a more secure way to write this?
Is it safe? Well it's safer than:
"regionmemberships.region_id = #{ params[:region_id] }
OR regionmemberships.region_id = #{ params[:other_id] }"
As it uses a parameterized query which prevents SQL injection. However you don't need to construct the SQL from a string in the first place:
results.joins(:regionmemberships)
.where(regionmemberships: { region_id: params[:region_id] })
You can also use a array with where:
results.joins(:regionmemberships)
.where(regionmemberships: { region_id: [1,2,5] })
And it will generate:
WHERE regionmemberships.region_id IN (1,2,5)
Which is better than using OR.
SELECT c.*, COUNT(m.cid) AS count FROM councils AS c LEFT JOIN membership AS m ON c.id = m.cid GROUP BY c.id
I can chain scopes like so:
scoped = User.where(sex: 'F')
scoped = scoped.where(color: 'blue')
ActiveRecord generates this SQL for the 1st line:
SELECT COUNT(*) FROM "users" WHERE "users"."sex" = 'F'
And this SQL for the 2nd line:
SELECT COUNT(*) FROM "users" WHERE "users"."sex" = 'F' AND "users"."color" = 'blue'
While keeping the initial query so it's built over 2 lines how can I change it from AND to OR so the end result looks like:
SELECT COUNT(*) FROM "users" WHERE "users"."sex" = 'F' OR "users"."color" = 'blue'
Without extensions, this is currently impossible with Rails unless you use raw SQL:
User.where('sex = ? OR color = ?', 'F', 'blue')
In Rails 5, these types of ORs will be supported out of the box.
If you use the squeel gem, you can implement this like so:
User.where(
{ sex: 'F' } |
{ color: 'blue' }
)
However, since it sounds like you need to build the query dynamically, I'd recommend doing something like this:
queries = [
"sex = 'F'",
"color = 'blue'"
]
User.where(queries.join(" OR "))
With this method, you can dynamically add as many queries as you need to the queries array. Obviously, you'll have to write the queries in raw SQL but this is the only option I'm aware of for accomplishing this with ActiveRecord.
UPDATE
To answer #Thomas question in the comments:
I'm wondering if there is a way to utilize join data with this. Like
user.sex instead of sex.
queries = [
"users.sex = 'F'",
"color = 'blue'"
]
Car.joins(:user).where(queries.join(" OR "))
What would be the best way of rewriting this query without interpolation?
def case_joins(type)
subquery = <<-SQL.squish
SELECT id FROM cases c2
WHERE c2.title_id = titles.id AND c2.value = 0 AND c2.type = '#{type}'
ORDER BY c2.created_at DESC LIMIT 1
SQL
"LEFT OUTER JOIN cases ON cases.title_id = titles.id AND cases.value = 0 AND cases.type = '#{type}' AND cases.id = (#{subquery})"
end
I'm assuming that you want to avoid interpolation of variables, which is dangerous since its open to SQL injection. I would simply join onto the cases selected from the subquery instead of putting the subquery into the WHERE conditions. This does involve interpolation, but only of AR-generated SQL. I would also implement it as a scope to leverage AR scope chaining:
class Title < ActiveRecord::Base
def self.case_joins(type)
case_query = Case.from("cases c").where(c: {title_id: title_id, value: 0, type: type}).order('c.created_at DESC').limit(1)
joins("LEFT OUTER JOIN (#{case_query.to_sql}) cases ON cases.title_id = titles.id")
end
end
This way, you can chain the scope to others like so:
Title.where(attribute1: value1).case_joins("typeA")
(Note that removed the superfluous WHERE conditions in the outer SELECT.)
It's difficult to infer what the rest of your code looks like, but I presume titles is being used in a query further up your call stack.
If you were to use ActiveRecord instead of native SQL, you could do something like this:
def case_joins(scope, title_id, type)
ids = Case.where(title_id: title_id, value: 0, type: type)
.order('created_at desc').limit(1).pluck(:id)
scope.joins('left outer join cases on cases.title_id = titles.id')
.where(value: 0, type: type, id: ids)
end
scope here is the current AR query you are modifying.
This is off the top of my head, so I'm not sure if the AR syntax above is correct, but it does avoid the need to interpolate SQL and also uses scoping.
To be honest, though, it's not all that much more readable than native SQL, and so YMMV. It does at least mean that (apart from the join) you're not encoding SQL in your code.
Here is modification of #eirikir's answer, that works the same way as method in question.
def case_joins(type)
case_query = Case.from("cases c").where('c.title_id = titles.id AND c.value = 0 AND c.type = ?', type).order('c.created_at DESC').select(:id).limit(1)
"LEFT OUTER JOIN cases ON cases.title_id = titles.id AND cases.id = (#{case_query.to_sql})"
end
How do i write this query in rails active record style
SELECT COUNT(o.id) no_of_orders,
SUM(o.total) total,
SUM(o.shipping) shipping
FROM orders o JOIN
(
SELECT DISTINCT order_id
FROM designer_orders
WHERE state IN('pending', 'dispatched', 'completed')
) d
ON o.id = d.order_id
You can do with an explicit query, you have 2 manner to do that:
Model.where("MYSQL_QUERY")
or
Model.find_by_sql("MYSQL_QUERY")
http://apidock.com/rails/ActiveRecord/Base/find_by_sql/class
OR
In Rails Style with a little more steps (probably can be done with less):
order_ids = DesignerOrder.where("state IN (?)", ['pending', 'dispatched', 'completed']).select(:order_id).distinct
partial_result = Order.where("id IN (?)", order_ids)
no_of_orders = partial_result.count
total_sum = partial_result.sum(:total)
shipping_sum = partial_result.sum(:shipping)
You can also do it like this
Order
.select('
COUNT(o.id) no_of_orders,
SUM(o.total) total,
SUM(o.shipping) shipping
')
.from('orders o')
.joins("
(#{
DesignerOrders
.select("DISTINCT order_id")
.where("state IN('pending', 'dispatched', 'completed')")
}) d on o.id = d.order_id
")
I didn't actually run this but the concept is valid. You don't even need an active record model if you use 'from'. We've used techniques like this to do AR style queries for extremely complex SQL and it's made our lives a lot easier.
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