活动记录中的动态 where 子句避免 sql 注入

Dynamic where clause in active record avoiding sql injection

你知道如何构建避免 sql 注入的动态查询吗 ?

property = 'foo'
value = 'bar'
SomeObject.where("#{property} > ?", value)
# works but permit sql inj
SomeObject.where(":property > :value", property: property, value: value)
# create select * from some_object where 'foo' > 'bar'
# and the 'foo' I need without the quotes
SomeObject.where(
  "#{SomeObject.connection.quote_column_name(property)} > :value", 
  value: value
)

更新

示例 1(尝试结束语句并注入新语句):

property = '; DROP TABLE users; --'
User.where("#{User.connection.quote_column_name(property)} > :value", value: 3)
# => SELECT "users".* FROM "users" WHERE ("; DROP TABLE users; --" > 3)

示例2(尝试结束列名引号):

property = '"; DELETE FROM users;--'
User.where("#{User.connection.quote_column_name(property)} > :value", value: 3)
# => SELECT "users".* FROM "users" WHERE ("""; DELETE FROM users;--" > 3)

一种简单但安全的方法是将允许的 属性 名称列入白名单:

PROPERTIES = ["foo", "bar", "baz"].freeze
def find_greater_than(property, value)
  raise "'#{property}' is not a valid property, only #{PROPERTIES.join(", ")} are allowed!" if !PROPERTIES.include?(property)
  SomeObject.where("#{property} > ?", value)
end

您可以(正如@engineersmnky 指出的那样)动态检查可用列:

raise "Some Message" if SomeObject.column_names.include?(property)

但我不喜欢这种方法,因为让列可搜索应该是一个决定,而不是自动的。

另一种方法是使用 Rails 提供的清理。

def find_greater_than(property, value)
  sanitized_property = ActiveRecord::Base.connection.quote_column_name(property)
  SomeObject.where("#{sanitized_property} > ?", value)
end

引用逻辑由数据库特定的连接适配器实现。

不确定您的用例,但 arel 可以像这样帮助您

some_object_table = SomeObject.arel_table
SomeObject.where(some_object_table[property.intern].gt(value))

这将使用您喜欢的所有转义 rails 适当地执行查询。

这是可行的,因为 arel 是 rails 使用的基础查询汇编器,所以 ActiveRecord where 子句可以毫无问题地理解 Arel::Nodes(实际上是如何将它们组装成开头)

还考虑到动态性质,您可能需要检查 property 是否为有效列以避免 SQL 级别的错误,例如

raise AgrumentError unless some_object_table.engine.columns.map {|c| c.name.intern}.include?(property.intern)
# or 
raise AgrumentError unless SomeObject.column_names.map(&:to_sym).include?(property.to_sym)

就我而言,我最后使用了

ActiveRecord::Base.connection.quote_table_name 函数在查询前清理列名。

property = 'foo'
value = 'bar'

sanitized_property = ActiveRecord::Base.connection.quote_table_name(property)
SomeObject.where("#{sanitized_property} > ?", value)

该函数可以处理 table 和列名定义。

属性 = 'table.column'

会产生

where `table`.`column` > `bar`