活动记录中的动态 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`
你知道如何构建避免 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`