问题
I've got a scope in my model :
scope :assigned_to_user, ->(user) {
task_table = UserTask.table_name
joins("INNER JOIN #{task_table}
ON #{task_table}.user_id = #{user.id}
AND (#{task_table}.type_id = #{table_name}.type_id)
AND (#{task_table}.manager_id = #{table_name}.manager_id)
")
}
So after running brakeman report I get this warning :
assigned_to_user | SQL Injection | Possible
So I tried the following :
scope :assigned_to_user, ->(user) {
task_table = UserTask.table_name
joins(ActiveRecord::Base::sanitize("INNER JOIN #{task_table}
ON #{task_table}.user_id = #{user.id}
AND (#{task_table}.type_id = #{table_name}.type_id)
AND (#{task_table}.manager_id = #{table_name}.manager_id)
"))
}
This doesn't work for me because it adds '
(apostrophe) to the front and back of the sql. So when I use this as a part of query which returns some results and I apply this scope it generates the incorrect sql.
I also tried this:
scope :assigned_to_user, ->(user) {
task_table = UserTask.table_name
joins("INNER JOIN #{task_table}
ON #{task_table}.user_id = ?
AND (#{task_table}.type_id = #{table_name}.type_id)
AND (#{task_table}.manager_id = #{table_name}.manager_id)
", user.id)
}
Doesn't even build the statement. And tried couple of other stuff which didn't work and not even worth mentioning. Does anybody have idea how to fix this?
回答1:
After some kind of research here is what I would use.
There is a method called sanitize_sql_array
(ref), you can use it to escape statements by passing an sql string and replacement values to it like:
sanitize_sql_array(['user_id = :user_id', user_id: 5])
# => "user_id = 5"
If we'd pass a table name to this method it will also escape it, but will apply a quote
method of ActiveRecord::Base.connection
object on a value, which is used to escape variables, but not table names. Maybe sometimes it will work, but it failed for me when I was using PostrgreSQL, because quote
method uses single quotes, but PostgreSQL requires double-quotation for table names.
sanitize_sql_array([
'INNER JOIN :table_name ON :table_name.user_id = :user_id',
{ table_name: 'users', user_id: 5 }
])
# => "INNER JOIN 'users' ON 'users'.user_id = 5"
connection
object also has a method quote_table_name
, which could be separately applied on table names, to make sure that they are escaped + use sanitize_sql_array
for user id.
scope :assigned_to_user, -> (user) {
task_table = connection.quote_table_name(UserTask.table_name)
current_table = connection.quote_table_name(table_name)
sanitized_sql = sanitize_sql_array([
"INNER JOIN #{task_table}
ON #{task_table}.user_id = :user_id
AND (#{task_table}.type_id = #{current_table}.type_id)
AND (#{task_table}.manager_id = #{current_table}.manager_id)",
{ user_id: user.id }
])
joins(sanitized_sql)
}
Or you could actually just use sanitize
on user.id
instead of wrapping everything in sanitize_sql_array
method call (#{sanitize(user.id)}
).
By the way, Brakeman won't already show any warnings, because query has been moved to a variable. Brakeman literally parses your code as is and it does not know about a variable and it's content. So all this thing is just to make yourself sure that everything is being escaped.
Just to shut up Brakeman you could just move a query to a variable:
scope :assigned_to_user, -> (user) {
task_table = UserTask.table_name
query = "INNER JOIN #{task_table}
ON #{task_table}.user_id = #{user.id}
AND (#{task_table}.type_id = #{table_name}.type_id)
AND (#{task_table}.manager_id = #{table_name}.manager_id)"
joins(query)
}
来源:https://stackoverflow.com/questions/27853371/rails-brakeman-warning-of-sql-injection