【问题标题】:Rails 5 SQL InjectionRails 5 SQL 注入
【发布时间】:2017-05-15 14:40:43
【问题描述】:

我已经在各种 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,因为我正在执行复杂的纬度/经度计算、聚合函数、复杂的子查询等。

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

方法一

对于这种方法,我不知道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

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

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

此外,sanitize 似乎代表quote。但是根据this SO post,它说简单地用单引号包裹东西是不安全的......所以我不知道。

方法二

我不确定这是否同样安全,但它似乎使用了一个实际的 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

用同样的方法:

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


一种方法比另一种方法更安全吗?两者都是 100% 安全的吗?

我的应用程序总是远远超出 Rails 的 SQL 能力,我需要一个可以包含在我所有项目中的好库,我知道它是完全安全的。

【问题讨论】:

    标签: ruby-on-rails ruby postgresql activerecord ruby-on-rails-5


    【解决方案1】:

    使用quote 是安全的。我在the page you linked to 上阅读了答案,我没有看到有人说quote 不安全。我看到你关于使用“引号”的问题。是的,如果你只是在字符串周围加上引号,那是不安全的,例如:

    q = "SELECT * FROM users where email = '#{params[:email]}'"
    

    但是使用quote(方法)就可以了:

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

    您可以在控制台中玩耍并尽力打破它,但我认为您无法做到:

    2.3.3 :003 > ActiveRecord::Base.connection.quote("f''oo")                                                                              
     => "'f''''oo'"
    

    如果你成功了,我相信 Rails 团队想知道(私下里)!但正如您所见,quote 方法不仅仅是在开头和结尾加上引号。

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

    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)
    

    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:
    

    (注意我在评论中显示quote_string,但您可能应该使用quote,它试图找出数据类型并做一些适当的事情。)

    顺便说一句,这里有一个与你类似的问题,我在 2014 年给出了答案,还有一些替代方案:How to execute a raw update sql with dynamic binding in rails

    【讨论】:

    • 我明白了——这是有道理的。那么sanitizesanitize_sql_for_conditions 之间的主要区别是什么?我忘记包含在我原来的问题中的另一部分是,method sanitize_sql_for_conditions 的文档提到:将它们清理为 WHERE 子句的有效 SQL 片段sanitize 的文档说 用于在 SQL SELECT 语句中使用对象之前对其进行清理。这是否意味着它们取决于情况,并且不能在 SQL 语句的任何地方使用一种方法? (在 SELECT、WHERE、GROUP BY 等中)。或者我可以在任何位置使用sanitize 吗?
    • 在我看来 sanitize_* 方法都受到保护,所以我认为您不打算使用它们。我一直认为quote 是用于此类事情的主要公共方法。事实上,简单的sanitize 方法只是调用quote(如你所说)。仅查看代码,似乎其他 sanitize_* 方法实际上是用于在 Railsy 数据结构(例如 {name: "foo", email: "a@b.com"})和 quote 之间进行桥接。他们在每个值上为您调用quote*for_conditions vs *for_assignment 似乎主要是关于使用, vs AND
    • 感谢您的帮助!很好的答案。
    • 我会说quote 用于转义值,您可以在其中编写文字(如字符串和数字)。如果您的用户还提供标识符(如表名和列名),您应该为这些使用其他内容。就我个人而言,我只会写一个非常严格的正则表达式,例如只允许字母和下划线。你也可以使用quote_table_name之类的东西。
    猜你喜欢
    • 2011-02-27
    • 2013-11-24
    • 2021-07-18
    • 2014-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-05-23
    • 2016-04-27
    相关资源
    最近更新 更多