当 Ecto 查询变得更复杂并且需要 CASE...WHEN...ELSE...END
之类的子句时,我们倾向于依赖 Ecto 的 fragment
来解决它。
例如query = from t in <Model>, select: fragment("SUM(CASE WHEN status = ? THEN 1 ELSE 0 END)", 2)
事实上,the most popular Stack Overflow post about this topic 建议创建一个这样的宏:
defmacro case_when(condition, do: then_expr, else: else_expr) do
quote do
fragment(
"CASE WHEN ? THEN ? ELSE ? END",
unquote(condition),
unquote(then_expr),
unquote(else_expr)
)
end
end
因此您可以在 Ecto 查询中以这种方式使用它:
query = from t in <Model>,
select: case_when t.status == 2
do 1
else 0
end
同时,在另一个post中,我发现了这个:
(Ecto.Query.CompileError) to prevent SQL injection attacks, fragment(...) does not allow strings to be interpolated as the first argument via the `^` operator, got: `"exists (\n SELECT 1\n FROM #{other_table} o\n WHERE o.column_name = ?)"
好吧,似乎 Ecto 的团队发现人们正在使用 fragment
来解决复杂的查询,但他们没有意识到它会导致 SQL 注入,因此他们不允许在那里使用字符串插值作为保护开发者。
然后另一个人说“别担心,use macros。”
我不是灵丹妙药专家,但这似乎是一种使用字符串插值的解决方法,逃避 fragment
保护。
有没有办法使用片段并确保查询参数化?
答案 0 :(得分:4)
此处的 SQL 注入将导致使用外部数据进行字符串插值。想象一下 where: fragment("column = '#{value}'")
(而不是正确的 where: fragment("column = ?", value)
),如果 value 来自您的 params
(Phoenix 操作的第二个参数的通常名称,即参数从 HTTP 请求中提取),是的,这可能会导致 SQL 注入。
但是,预处理语句的问题在于,您不能用某些动态 SQL 部分(例如,像运营商)所以,你真的没有选择。假设您想编写 ?
因为 operator 是动态的并且取决于条件,只要 operator 不是来自用户(在某处硬编码)在您的代码中),这将是安全的。
我不知道你是否熟悉 PHP(以下示例中的 PDO),但这与 fragment/1
完全相同(通过字符串插值注入一个值)与 fragment("column #{operator} ?", value)
相反然后 $bdd->query("... WHERE column = '{$_POST['value']}'")
(正确的准备语句)。但是,如果我们回到我之前关于动态运算符的故事,如前所述,您不能动态绑定一些随机 SQL 片段,DBMS 会将 $stmt = $bdd->prepare('... WHERE column = ?')
与 $stmt->execute([$_POST['value']]);
解释为运算符和 {{1 }} 作为值(对于这个想法)"WHERE column ? ?"
这在语法上是不正确的。因此,将这个运算符动态化的最简单方法是编写 >
(通过字符串插值或连接注入它,但仅限于它)。如果这个变量 'foo'
是由你自己的代码定义的(例如:WHERE column '>' 'foo'
),那很好,但相反,如果它涉及一些来自客户端的超全局变量,如 "WHERE column {$operator} ?"
或$operator
,这会造成安全漏洞(SQL 注入)。
TL;博士
<块引用>然后另一个人说“别担心,use macros。”
Aleksei Matiushkin 在提到的帖子中的回答只是通过 $operator = some_condition ? '>' : '=';
动态注入 known 运算符来解决禁用/禁止字符串插值的一种解决方法。如果你重复使用这个技巧(并且不能真的这样做),只要你不盲目地“注入”来自用户的任何随机值,你会没事的。
更新:
毕竟,$_POST
(我没有检查源代码)似乎并不意味着准备好的语句($_GET
不是真正的准备好的语句的占位符)。我尝试了一些简单而愚蠢的查询,如下所示:
fragment/1
至少在 PostgreSQL/postgrex 中,控制台中生成的查询实际上是这样的:
SELECT ... FROM "customers" AS c0 WHERE (lastname 'LIKE' '%') []
请注意参数末尾的 fragment/1
(空列表)(并且查询中没有 ?
),因此它似乎就像 PHP/PDO 中准备好的语句的模拟,意思是 Ecto (或 postgrex?)直接在查询中实现正确的转义和值注入,但是,如上所述,from(
Customer,
where: fragment("lastname ? ?", "LIKE", "%")
)
|> Repo.all()
变成了一个字符串(参见围绕它的 []
),而不是一个运算符,因此查询失败有语法错误。