Django extra + where:如何转义标识符

Posted

技术标签:

【中文标题】Django extra + where:如何转义标识符【英文标题】:Django extra + where: how to escape identifiers 【发布时间】: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 的元组)

【问题讨论】:

根据文档,extra 已被弃用,他们希望在需要的情况下获得反馈:docs.djangoproject.com/en/3.2/ref/models/querysets/#extra 当然,您始终可以通过拥有有效表名列表并在其中查找表名来自行解决,但您可能不想这样做 【参考方案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)

【讨论】:

以上是关于Django extra + where:如何转义标识符的主要内容,如果未能解决你的问题,请参考以下文章

django-extra查询

Django框架 之 查询 Extra

Django之extra过滤

Django 补充ORM的extra过滤

Django中的sql注入

Web框架开发-Django-extra过滤