Rails - 如何防止postgres find_by_sql查询的sql注入?

时间:2014-05-03 17:04:15

标签: sql ruby-on-rails postgresql sql-injection rails-activerecord

我对postgres DB有以下find_by_sql查询:

pick_ids = picks.pluck(:id).join(',')

Pick.find_by_sql("WITH cte1 AS (SELECT DISTINCT user_id, state, pick FROM picks 
WHERE id IN (#{pick_ids})), cte2 AS (SELECT user_id, coalesce(sum(amount_won), 0)
as picks_total_won FROM picks WHERE id IN (#{pick_ids}) GROUP BY user_id), cte3 AS 
(SELECT user_id, COUNT(CASE WHEN state = 'won' then 1 ELSE null END) AS picks_won,
FROM cte1 GROUP BY user_id) SELECT cte2.picks_total_won, cte3.picks_won,
FROM cte2 INNER JOIN cte3 ON cte2.user_id = cte3.user_id")

当我尝试对其进行参数化(即?,?... pick_ids,pick_ids)时,我收到以下错误:

ArgumentError: wrong number of arguments (3 for 1..2)

(1)你能像这样参数化一个find_by_sql查询吗?如果是这样,怎么样?

(2)如果查询永远不会收到用户输入的参数,你甚至需要担心SQL注入吗?

2 个答案:

答案 0 :(得分:3)

首先,您可能希望在SQL中使用%Q{...}字符串和一些格式以避免无法读取的混乱:

Pick.find_by_sql(%Q{
  WITH
    cte1 AS (
      SELECT DISTINCT user_id, state, pick
      FROM picks 
      WHERE id IN (#{pick_ids})
    ),
    cte2 AS (
      SELECT user_id, coalesce(sum(amount_won), 0) as picks_total_won
      FROM picks
      WHERE id IN (#{pick_ids})
      GROUP BY user_id
    ),
    cte3 AS (
      SELECT user_id, COUNT(CASE WHEN state = 'won' then 1 ELSE null END) AS picks_won,
      FROM cte1
      GROUP BY user_id
    )
  SELECT cte2.picks_total_won, cte3.picks_won
  FROM cte2
  JOIN cte3 ON cte2.user_id = cte3.user_id
})

根据您picks的来源,您可以完全避免插值并嵌入SQL以生成pick_ids(可能使用其他CTE)。

如果您需要从外部世界提供pick_ids,那么您可以使用find_by_sql的占位符,但界面有点奇怪:您必须将数组传递给find_by_sql

Pick.find_by_sql([%Q{
  WITH
    cte1 AS (
      SELECT DISTINCT user_id, state, pick
      FROM picks 
      WHERE id IN (:pick_ids)
    ),
    cte2 AS (
      SELECT user_id, coalesce(sum(amount_won), 0) as picks_total_won
      FROM picks
      WHERE id IN (:pick_ids)
      GROUP BY user_id
    ),
    cte3 AS (
      SELECT user_id, COUNT(CASE WHEN state = 'won' then 1 ELSE null END) AS picks_won,
      FROM cte1
      GROUP BY user_id
    )
  SELECT cte2.picks_total_won, cte3.picks_won
  FROM cte2
  JOIN cte3 ON cte2.user_id = cte3.user_id
}, :pick_ids => some_array_of_ids])

请注意参数列表中[]的位置。

答案 1 :(得分:0)

您也可以使用Pick.sanitize(str)插入任意文本。

所以,暂时忽略改善mu提到的查询的所有其他方法,它将是:

Pick.find_by_sql("WITH cte1 AS (SELECT DISTINCT user_id, state, pick FROM picks 
WHERE id IN (#{Pick.sanitize pick_ids})), cte2 AS (SELECT user_id, coalesce(sum(amount_won), 0)
as picks_total_won FROM picks WHERE id IN (#{Pick.sanitize pick_ids}) GROUP BY user_id), cte3 AS 
(SELECT user_id, COUNT(CASE WHEN state = 'won' then 1 ELSE null END) AS picks_won,
FROM cte1 GROUP BY user_id) SELECT cte2.picks_total_won, cte3.picks_won,
FROM cte2 INNER JOIN cte3 ON cte2.user_id = cte3.user_id")