Ecto 的片段允许 SQL 注入

时间:2021-05-13 14:33:30

标签: erlang elixir phoenix-framework sql-injection ecto

当 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 保护。

有没有办法使用片段并确保查询参数化?

1 个答案:

答案 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() 变成了一个字符串(参见围绕它的 []),而不是一个运算符,因此查询失败有语法错误。