Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails brakeman warning of sql injection

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?

like image 801
Gandalf StormCrow Avatar asked Jan 09 '15 03:01

Gandalf StormCrow


People also ask

Does rails prevent SQL injection?

Summary. As you can see, Ruby on Rails, by default, won't save you from all SQL injection attack attempts. Some of the methods of its built-in library, Active Records, prevent these types of attacks automatically, but others don't. However, it's not that difficult to make your application secure.

What is the primary cause of SQL injection vulnerabilities in Javascript?

The three root causes of SQL injection vulnerabilities are the combining of data and code in dynamic SQL statement, error revealation, and the insufficient input validation.


1 Answers

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)
}
like image 188
Michael Radionov Avatar answered Oct 04 '22 18:10

Michael Radionov