Rails. Как удалить кавычки из условий запроса при использовании заполнителя и строки? - PullRequest
1 голос
/ 19 января 2012

Я пытаюсь создать форму поиска (rails 3.1), в которой один из параметров поиска позволяет пользователю выбирать математические символы, такие как <,>, = и т. Д. Затем я хочу использовать значение, выбранное в качестве части моего запроса , Единственная проблема заключается в том, что он ставит кавычки вокруг него и приводит к неверному sql.

упрощенный пример

params[:comparison] = '>'
params[:rank] = '3'

.where("rank ? ?", params[:comparison], params[:rank].to_i)

Результаты в

PGError: ERROR:  syntax error at or near "3"
LINE 1: ... WHERE (rank '>' 3)

Я хочу сделать так, чтобы

WHERE (rank > 3)

Как я могу создать этот запрос активной записи без кавычек вокруг символа больше, чтобы аргумент был безопасным и не уязвимым для эксплойтов SQL-инъекций?

Ответы [ 2 ]

3 голосов
/ 19 января 2012

В этом очень специфическом случае я бы посоветовал вам просто проверить значение params[:comparison], поскольку вы можете легко "внести его в белый список" по отношению к известным безопасным значениям, которые, как вы ожидаете, равны <,> и =

Пример кода:

known_comparisons = %w{< > =}
params_comparison = ">"

if known_comparisons.any? { |i| i === params_comparison }
  puts "were good"
else
  puts "bad value"
end

Затем вставьте значение напрямую с помощью интерполяции строк, поскольку теперь вы уверены, что это безопасно.

.where("rank #{params[:comparison]} ?", params[:rank].to_i)
0 голосов
/ 19 января 2012

Эта проблема напоминает мне класс запросов в Redmine. Исходный код на здесь .

class Query < ActiveRecord::Base

@@operators = { "="   => :label_equals,
                "!"   => :label_not_equals,
                "o"   => :label_open_issues,
                "c"   => :label_closed_issues,
                "!*"  => :label_none,
                "*"   => :label_all,
                ">="  => :label_greater_or_equal,
                "<="  => :label_less_or_equal,
                "<t+" => :label_in_less_than,
                ">t+" => :label_in_more_than,
                "t+"  => :label_in,
                "t"   => :label_today,
                "w"   => :label_this_week,
                ">t-" => :label_less_than_ago,
                "<t-" => :label_more_than_ago,
                "t-"  => :label_ago,
                "~"   => :label_contains,
                "!~"  => :label_not_contains }

cattr_reader :operators

@@operators_by_filter_type = { :list => [ "=", "!" ],
                               :list_status => [ "o", "=", "!", "c", "*" ],
                               :list_optional => [ "=", "!", "!*", "*" ],
                               :list_subprojects => [ "*", "!*", "=" ],
                               :date => [ "<t+", ">t+", "t+", "t", "w", ">t-", "<t-", "t-" ],
                               :date_past => [ ">t-", "<t-", "t-", "t", "w" ],
                               :string => [ "=", "~", "!", "!~" ],
                               :text => [  "~", "!~" ],
                               :integer => [ "=", ">=", "<=", "!*", "*" ] }


def statement
  # filters clauses
  filters_clauses = []
  filters.each_key do |field|
    next if field == "subproject_id"
    v = values_for(field).clone
    next unless v and !v.empty?
    operator = operator_for(field)

    # "me" value subsitution
    if %w(assigned_to_id author_id watcher_id).include?(field)
      v.push(User.current.logged? ? User.current.id.to_s : "0") if v.delete("me")
    end

    sql = ''
    if field =~ /^cf_(\d+)$/
      # custom field
      db_table = CustomValue.table_name
      db_field = 'value'
      is_custom_filter = true
      sql << "#{Issue.table_name}.id IN (SELECT #{Issue.table_name}.id FROM #{Issue.table_name} LEFT OUTER JOIN #{db_table} ON #{db_table}.customized_type='Issue' AND #{db_table}.customized_id=#{Issue.table_name}.id AND #{db_table}.custom_field_id=#{$1} WHERE "
      sql << sql_for_field(field, operator, v, db_table, db_field, true) + ')'
    elsif field == 'watcher_id'
      db_table = Watcher.table_name
      db_field = 'user_id'
      sql << "#{Issue.table_name}.id #{ operator == '=' ? 'IN' : 'NOT IN' } (SELECT #{db_table}.watchable_id FROM #{db_table} WHERE #{db_table}.watchable_type='Issue' AND "
      sql << sql_for_field(field, '=', v, db_table, db_field) + ')'
    elsif field == "member_of_group" # named field
      if operator == '*' # Any group
        groups = Group.all
        operator = '=' # Override the operator since we want to find by assigned_to
      elsif operator == "!*"
        groups = Group.all
        operator = '!' # Override the operator since we want to find by assigned_to
      else
        groups = Group.find_all_by_id(v)
      end
      groups ||= []

      members_of_groups = groups.inject([]) {|user_ids, group|
        if group && group.user_ids.present?
          user_ids << group.user_ids
        end
        user_ids.flatten.uniq.compact
      }.sort.collect(&:to_s)

      sql << '(' + sql_for_field("assigned_to_id", operator, members_of_groups, Issue.table_name, "assigned_to_id", false) + ')'

    elsif field == "assigned_to_role" # named field
      if operator == "*" # Any Role
        roles = Role.givable
        operator = '=' # Override the operator since we want to find by assigned_to
      elsif operator == "!*" # No role
        roles = Role.givable
        operator = '!' # Override the operator since we want to find by assigned_to
      else
        roles = Role.givable.find_all_by_id(v)
      end
      roles ||= []

      members_of_roles = roles.inject([]) {|user_ids, role|
        if role && role.members
          user_ids << role.members.collect(&:user_id)
        end
        user_ids.flatten.uniq.compact
      }.sort.collect(&:to_s)

      sql << '(' + sql_for_field("assigned_to_id", operator, members_of_roles, Issue.table_name, "assigned_to_id", false) + ')'
    else
      # regular field
      db_table = Issue.table_name
      db_field = field
      sql << '(' + sql_for_field(field, operator, v, db_table, db_field) + ')'
    end
    filters_clauses << sql

  end if filters and valid?

  filters_clauses << project_statement
  filters_clauses.reject!(&:blank?)

  filters_clauses.any? ? filters_clauses.join(' AND ') : nil
end

private

# Helper method to generate the WHERE sql for a +field+, +operator+ and a +value+
def sql_for_field(field, operator, value, db_table, db_field, is_custom_filter=false)
  sql = ''
  case operator
  when "="
    if value.any?
      sql = "#{db_table}.#{db_field} IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + ")"
    else
      # IN an empty set
      sql = "1=0"
    end
  when "!"
    if value.any?
      sql = "(#{db_table}.#{db_field} IS NULL OR #{db_table}.#{db_field} NOT IN (" + value.collect{|val| "'#{connection.quote_string(val)}'"}.join(",") + "))"
    else
      # NOT IN an empty set
      sql = "1=1"
    end
  when "!*"
    sql = "#{db_table}.#{db_field} IS NULL"
    sql << " OR #{db_table}.#{db_field} = ''" if is_custom_filter
  when "*"
    sql = "#{db_table}.#{db_field} IS NOT NULL"
    sql << " AND #{db_table}.#{db_field} <> ''" if is_custom_filter
  when ">="
    sql = "#{db_table}.#{db_field} >= #{value.first.to_i}"
  when "<="
    sql = "#{db_table}.#{db_field} <= #{value.first.to_i}"
  when "o"
    sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_false}" if field == "status_id"
  when "c"
    sql = "#{IssueStatus.table_name}.is_closed=#{connection.quoted_true}" if field == "status_id"
  when ">t-"
    sql = date_range_clause(db_table, db_field, - value.first.to_i, 0)
  when "<t-"
    sql = date_range_clause(db_table, db_field, nil, - value.first.to_i)
  when "t-"
    sql = date_range_clause(db_table, db_field, - value.first.to_i, - value.first.to_i)
  when ">t+"
    sql = date_range_clause(db_table, db_field, value.first.to_i, nil)
  when "<t+"
    sql = date_range_clause(db_table, db_field, 0, value.first.to_i)
  when "t+"
    sql = date_range_clause(db_table, db_field, value.first.to_i, value.first.to_i)
  when "t"
    sql = date_range_clause(db_table, db_field, 0, 0)
  when "w"
    first_day_of_week = l(:general_first_day_of_week).to_i
    day_of_week = Date.today.cwday
    days_ago = (day_of_week >= first_day_of_week ? day_of_week - first_day_of_week : day_of_week + 7 - first_day_of_week)
    sql = date_range_clause(db_table, db_field, - days_ago, - days_ago + 6)
  when "~"
    sql = "LOWER(#{db_table}.#{db_field}) LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
  when "!~"
    sql = "LOWER(#{db_table}.#{db_field}) NOT LIKE '%#{connection.quote_string(value.first.to_s.downcase)}%'"
  end

  return sql
end

...

end
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...