【问题标题】:Django extra + where: how to escape identifiersDjango extra + where:如何转义标识符
【发布时间】:2021-11-16 18:43:36
【问题描述】:

我在 Django 中有一个带有 where 子句的 extra 过滤器,但表名是动态的。

filtered_queryset = queryset.extra(
    where=[
        f'({table_name}.modified_on, {table_name}.id) > (%s, %s)',
    ],
    params=(after_ts, after_id),
)

我怎样才能最好地避免 f-string 以确保它不对 SQL 注入开放?

我希望 SQL 具有元组比较,而不是多个 >>=AND。从之前的测试来看,它似乎更有可能使用多列索引。

(这是一些自定义分页代码的一部分,其中光标本质上是一个日期时间和 id 的元组)

【问题讨论】:

标签: python sql django postgresql sql-injection


【解决方案1】:

为什么不把它写成过滤器呢?

filtered_queryset = queryset.filter(
    Q(modified_on__gt=after_ts) |
    Q(Q(modified_on__gte=after_ts) & Q(id__gt=after_id))
)

PS:有点不清楚您的查询要做什么,我认为是这样,但也许您想过滤其他内容。

【讨论】:

  • 我在这个问题中添加了更多细节,说明我为什么要避免这种情况
  • 我不知道如何强制 Django 使用某种 SQL,所以恐怕我无法进一步帮助您。 :-(
【解决方案2】:

首先,根据已知表名列表验证您的 table_name 变量。

另外,使用双引号作为标识符分隔符,这允许table_name 成为 SQL 保留关键字,或者包含空格或标点符号。只要使用标识符分隔符,这些在 SQL 中都是合法的。

    f'("{table_name}".modified_on, "{table_name}".id) > (%s, %s)',

https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS

但是,如果您的表名包含文字 " 字符,这可能会导致麻烦,因为 " 符号将被解释为终止标识符分隔符。因此,您可以自行过滤table_name,将文字" 替换为""

【讨论】:

    【解决方案3】:

    this answer 一样,extra 可以通过使用annotate 来避免,并且表名取自 Queryset 内部的模型,这样就不必担心转义任何标识符:

    from django.db.models import F, Func, TextField
    
    col_a_col_b = Func(F('col_a'), F('col_b'), function='ROW', output_type=TextField())
    col_a_col_b_from = Func(col_a_value, col_b_value, function='ROW')
    
    filtered_queryset = queryset
        .annotate(col_a_col_b=col_a_col_b)
        .filter(col_a_col_b__gt=col_a_col_b_from)
        .order_by('col_a', 'col_b')
    

    (显然在 Django 3.2+ 中可以使用alias 代替extra,并且避免了output_field hack)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2023-03-15
      • 2013-01-04
      • 1970-01-01
      • 2014-03-06
      • 1970-01-01
      • 1970-01-01
      • 2020-02-05
      • 2017-04-05
      相关资源
      最近更新 更多