0

我对一个Postgres数据库以下的find_by_sql查询:Rails - 如何防止postgres find_by_sql查询的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注入吗?

回答

3

首先,你可能想使用一个%Q{...}字符串和一些格式化你的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 
}) 

视您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]) 

注位置在参数列表中的[]

+0

太神奇了!很好的答案...应该对他人有用 – keruilin

0

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

所以,暂时忽略所有其他的方式来改善这种亩提到的查询,这将是:

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")