Rails 5 SQL注入

Tal*_*boy 16 ruby postgresql activerecord ruby-on-rails ruby-on-rails-5

我已经在一段时间内阅读了关于各种SO线程,指南等的内容......但所有答案都是矛盾和矛盾的.

似乎有很多类似的方法,很多答案都说使用不同的方法.

  • sanitize
  • sanitize_conditions
  • sanitize_sql
  • sanitize_sql_array
  • sanitize_sql_for_assignment
  • sanitize_sql_for_conditions
  • sanitize_sql_hash
  • sanitize_sql_hash_for_assignment
  • sanitize_sql_hash_for_conditions
  • sanitize_sql_like

我正在尝试编写一个"原始查询"适配器,它允许我运行原始的Postgres查询,但允许我插入来自危险用户输入的我自己的参数.

我不能在这几个实例中使用AR,因为我正在进行复杂的纬度/经度计算,聚合函数,复杂子查询等.

到目前为止,我尝试了两种方法:

方法1

对于这种方法,我不知道是否sanitize是上述的最佳选择,或者它是否适用于100%的情况......(我只使用Postgres)

class RawQuery

  def exec(prepared, *params)
    prepared = query.dup
    params.flatten.each_with_index do |p, i|
      prepared.gsub!("$#{i + 1}", ActiveRecord::Base.sanitize(p))
    end
    ActiveRecord::Base.connection.exec_query(prepared)
  end

end
Run Code Online (Sandbox Code Playgroud)

琐碎的用法示例(当然通常不会这么简单,或者我只会使用AR):

RawQuery.new.exec('SELECT * FROM users WHERE name = $1', params[:name])

此外,它似乎sanitize代表quote.但根据这篇SO帖子说它只是用单引号包装东西并不安全......所以我不知道.

方法2

我不确定这是否同样安全,但它似乎使用了一个实际的PG准备功能(我假设它是100%安全的).唯一的问题是rails不会将其打印到控制台,也不包括SQL执行时间(这会破坏我的分析工具).

class RawQuery

  def prepare(query, *params)
    name = "raw_query_#{SecureRandom.uuid.gsub('-', '')}"
    connection = ActiveRecord::Base.connection.raw_connection
    connection.prepare(name, query)
    connection.exec_prepared(name, params)
  end

end
Run Code Online (Sandbox Code Playgroud)

使用方式相同:

RawQuery.new.prepare('SELECT * FROM users WHERE name = $1', params[:name])


一种方法比另一种更安全吗?100%安全吗?

我的应用程序总是远远超出Rails的SQL能力范围,我需要一个好的库,我可以包含在我认为完全安全的所有项目中.

Pau*_*rth 11

使用quote是安全的.我在你链接的页面上阅读了答案,我没有看到有人说这quote是不安全的.我看到你关于使用"引号"的问题.是的,如果您只是在字符串周围加上引号,那就是不安全,例如:

q = "SELECT * FROM users where email = '#{params[:email]}'"
Run Code Online (Sandbox Code Playgroud)

但使用quote(方法)很好:

q = "SELECT * FROM users where email = #{connection.quote(params[:email])}"
Run Code Online (Sandbox Code Playgroud)

你可以在控制台中玩游戏并尽力打破它,但我认为你不能:

2.3.3 :003 > ActiveRecord::Base.connection.quote("f''oo")                                                                              
 => "'f''''oo'"
Run Code Online (Sandbox Code Playgroud)

如果你成功了,我确信Rails团队想知道(私下)!但正如您所看到的,该quote方法不仅仅是在开头和结尾都贴上引号.

此外,由于您说您正在寻找权威引文,因此源代码本身中的注释表明引用用户输入是这些函数的预期目的:

https://github.com/rails/rails/blob/2471e6391dfe71cfbb8621bdf573729d961d3209/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb#L6-L13

# Quotes the column value to help prevent
# {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
def quote(value)
Run Code Online (Sandbox Code Playgroud)

https://github.com/rails/rails/blob/0f1d0b1b5254e3678abaabbebb3362a100c10262/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb#L17-L20

# Quotes strings for use in SQL input.
def quote_string(s) #:nodoc:
Run Code Online (Sandbox Code Playgroud)

(注意我正在显示quote_string注释,但您应该使用quote,它会尝试找出数据类型并执行适当的操作.)

顺便说一句,这里有一个与你类似的问题,我在2014年得到了答案,还有一些替代方案:如何在rails中使用动态绑定执行原始更新sql

  • 在我看来,`sanitize_*`方法都受到保护,所以我不认为你打算使用它们.我一直都认为`quote`是用于此类事情的主要公共方法.事实上,简单的`sanitize`方法只是调用`quote`(如你所说).只看代码,似乎其他`sanitize_*`方法实际上是用于Railsy数据结构之间的桥接(例如`{name:"foo",email:"a@b.com"}`)和`quote` .他们在每个值上为你调用`quote`.`*for_conditions` vs`*for_assignment`似乎主要是关于使用`,`vs`AND`. (2认同)