如何将 exec_query 与动态 SQL 一起使用
Posted
技术标签:
【中文标题】如何将 exec_query 与动态 SQL 一起使用【英文标题】:How to use exec_query with dynamic SQL 【发布时间】:2021-10-22 08:24:15 【问题描述】:我正在处理一个查询,并使用 exec_query
进行绑定以避免潜在的 SQL 注入。但是,我在尝试检查 id 是否在数组中时遇到了问题。
SELECT JSON_AGG(agg_date)
FROM (
SELECT t1.col1, t1.col2, t2.col1, t2.col2, t3.col3, t3.col4, t4.col7, t4.col8, t5.col5, t5.col6
FROM t1
JOIN t2 ON t1.id = t2.t1_id
JOIN t3 ON t1.id = t3.t3_id
JOIN t4 ON t2.is = t4.t2_id
JOIN t5 ON t3.id = t5.t3_id
WHERE t2.id IN ($1) AND t4.id = $2
) agg_data
这给出了invalid input syntax for integer: '1,2,3,4,5'
的错误
而SELECT ... WHERE t.id = ANY($1)
给出ERROR: malformed array literal: "1,2,3,4,5,6,7" DETAIL: Array value must start with "" or dimension information.
如果我在绑定变量周围添加花括号,我会得到 invalid input syntax for integer: "$1"
这是我使用exec_query
的方式
connection.exec_query(<<~EOQ, "-- CUSTOM SQL --", [[nil, array_of_ids], [nil, model_id]], prepare: true)
SELECT ... WHERE t.id IN ($1)
EOQ
我尝试过使用普通插值,但会引发有关 sql 注入的刹车错误,所以我不能使用这种方式:(
非常感谢任何有关能够进行此检查的帮助。如果exec_query
是错误的解决方法,我肯定会尝试其他事情:D
在我的课堂上,我使用 AR 的内部 sql 注入预防来搜索第一个绑定变量 id,然后提取 id 并加入到 sql 查询的字符串中。我对另一个绑定变量做同样的事情,找到对象并使用那个 id。只是作为进一步的预防措施。因此,当用户输入用于查询时,他们已经通过了 AR。这是一个刹车员扫描,它抛出了错误。我在星期一与我们的安全团队开会讨论这个问题,但也想在这里查看:D
【问题讨论】:
你能告诉我们实际的查询吗?仅凭图片的一小部分就很难真正提供一个像样的答案?是不是可以用 AR 查询界面和 Arel 而不是字符串来组合? 我认为 AR 的性能不会那么高,而且我真的不需要任何一种特定的模型。我更新了问题以显示我正在尝试做的事情的基础。我需要 json agg 数据,但认为 AR 无法做到这一点或性能不如原始 sql 【参考方案1】:让 Rails 为您完成清理工作:
ar = [1,2,8,9,100,800]
MyModel.where(id: ar)
您对 sql 注入的担忧表明 ar 源自用户输入。这是多余的,但也许想确保它是一个整数列表。 ar = user_ar.map(&:to_i)
.
# with just Rails sanitization
ar = "; drop table users;" # sql injection
MyModel.where(id: ar)
# query is:
# SELECT `my_models`.* from `my_models` WHERE `my_models`.`id` = NULL;
# or
ar = [1,2,8,100,"; drop table users;"]
MyModel.where(id: ar)
# query is
# SELECT `my_models`.* from `my_models` WHERE `my_models`.`id` in (1,2,8,100);
Rails 已为您服务!
【讨论】:
我知道内置 SQL 注入预防的 AR。我会更新这个问题,但我有一个我认为对于 AR 来说有点过于复杂的选择。另外,我认为由于我不需要数据库中的实际 AR 对象,我觉得原始 sql 性能更好。 感谢您的建议。我正在使用 AR 来清理输入:P 我正在获取 id 集合,找到 AR 对象集合,然后将 id 重新加入到原始查询的字符串中。我希望找到一种我不必这样做的方法。但是我在工作中与我的安全人员会面,他们告诉我,由于我的预处理,这是刹车员的误报。感谢您加强我的选择:D【参考方案2】:使用 Arel,您可以将查询编写为:
class Aggregator
def initialize(connection: ActiveRecord::Base.connection)
@connection = connection
@t1 = Arel::Table.new('t1')
@t2 = Arel::Table.new('t2')
@t3 = Arel::Table.new('t3')
@t4 = Arel::Table.new('t4')
@t5 = Arel::Table.new('t5')
@columns = [
:col1,
:col2,
@t2[:col1],
@t2[:col2],
@t3[:col3],
@t3[:col4],
@t4[:col7],
@t4[:col8],
@t5[:col5],
@t5[:col6]
]
end
def query(t2_ids:, t4_id:)
agg_data = t1.project(*columns)
.where(
t2[:id].in(t2_ids)
.and(t4[:id].eq(t4_id))
)
.join(t2).on(t1[:id].eq(t2[:t1_id]))
.join(t3).on(t1[:id].eq(t3[:t1_id]))
.join(t4).on(t1[:id].eq(t4[:t1_id]))
.join(t5).on(t1[:id].eq(t5[:t1_id]))
.as('agg_data')
yield agg_data if block_given?
t1.project('JSON_AGG(agg_data)')
.from(agg_data)
end
def exec_query(t2_ids:, t4_id:)
connection.exec_query(
query(t2_ids: t2_ids, t4_id: t4_id),
"-- CUSTOM SQL --"
)
end
private
attr_reader :connection, :t1, :t2, :t3, :t4, :t5, :columns
end
当然,设置一些模型会更简洁,这样你就可以做t1.joins(:t2, :t3, :t4, ...)
。您的性能问题是毫无根据的,因为 ActiveRecord 有很多方法可以查询和获取原始结果,而不是模型实例。
为WHERE IN ()
条件使用绑定变量有些问题,因为您必须使用与列表中元素数量匹配的绑定变量:
irb(main):118:0> T1.where(id: [1, 2, 3])
T1 Load (0.2ms) SELECT "t1s".* FROM "t1s" WHERE "t1s"."id" IN (?, ?, ?) /* loading for inspect */ LIMIT ?
这意味着您在准备查询时必须事先知道绑定变量的数量。作为一个 hacky 解决方法,您可以使用一些创造性的类型转换来让 Postgres 将逗号分隔的字符串拆分为一个数组:
class Aggregator
# ...
def query
agg_data = t1.project(*columns)
.where(
t2[:id].eq('any (string_to_array(?)::int[])')
.and(t4[:id].eq(Arel::Nodes::BindParam.new('$2')))
)
.join(t2).on(t1[:id].eq(t2[:t1_id]))
.join(t3).on(t1[:id].eq(t3[:t1_id]))
.join(t4).on(t1[:id].eq(t4[:t1_id]))
.join(t5).on(t1[:id].eq(t5[:t1_id]))
.as('agg_data')
yield agg_data if block_given?
t1.project('JSON_AGG(agg_data)')
.from(agg_data)
end
def exec_query(t2_ids:, t4_id:)
connection.exec_query(
query,
"-- CUSTOM SQL --"
[
[t2_ids.map |id| Arel::Nodes.build_quoted(id) .join(',')],
[t4_id]
]
)
end
# ...
end
【讨论】:
以上是关于如何将 exec_query 与动态 SQL 一起使用的主要内容,如果未能解决你的问题,请参考以下文章
如何将 Spring AbstractRoutingDataSource 与动态数据源一起使用?
如何在 Sql Server Compact Edition 中将参数与 LIKE 一起使用
我们可以将谷歌云 SQL 与 Amazon Elastic Beanstalk 一起使用吗
如何将变量值与 select 语句的结果一起放入 sql 表中?