数据库
首页 > 数据库> > python – 复杂Func的Django自定义(sql函数)

python – 复杂Func的Django自定义(sql函数)

作者:互联网

在为Django ORM order by exact找到解决方案的过程中,我创建了一个自定义的django Func:

from django.db.models import Func

class Position(Func):
    function = 'POSITION'
    template = "%(function)s(LOWER('%(substring)s') in LOWER(%(expressions)s))"
    template_sqlite = "instr(lower(%(expressions)s), lower('%(substring)s'))"

    def __init__(self, expression, substring):
        super(Position, self).__init__(expression, substring=substring)

    def as_sqlite(self, compiler, connection):
        return self.as_sql(compiler, connection, template=self.template_sqlite)

其工作原理如下:

class A(models.Model):
    title = models.CharField(max_length=30)

data = ['Port 2', 'port 1', 'A port', 'Bport', 'Endport']
for title in data:
    A.objects.create(title=title)

search = 'port'
qs = A.objects.filter(
        title__icontains=search
    ).annotate(
        pos=Position('title', search)
    ).order_by('pos').values_list('title', flat=True)
# result is
# ['Port 2', 'port 1', 'Bport', 'A port', 'Endport'] 

但正如@hynekcer评论的那样:

“It crashes easily by ') in '') from myapp_suburb; drop ...
expected that the name of the app is “myapp and autocommit is enabled.”

主要问题是额外的数据(子串)进入模板而没有sqlescape,这使得应用程序容易受到SQL注入攻击.

我找不到哪种方法可以保护Django.

我创建了一个repo (djposfunc),你可以测试任何解决方案.

解决方法:

TL; DR:
在Django文档中使用Func()的所有示例都可以轻松地用于使用一个参数安全地实现其他类似的SQL函数.
所有内置的Django database fuctionsconditional functions都是Func()的后代,设计也很安全.超出此限制的申请需要评论.

Func()类是Django Query表达式中最常用的部分.它允许以某种方式将几乎任何函数或运算符实现到Django ORM中.它就像一把瑞士军刀,非常普遍,但与专业工具(如带光学屏障的电动切割机)相比,必须更加注意不切割自己.如果曾经“升级”的“安全”小刀不适合放入口袋,那么用锤子从铁片上锻造自己的工具仍然要安全得多.

安全说明

>首先应阅读带有示例的Func(*expressions, **extra)简短文档. (我在这里推荐Django 2.0的开发文档,其中最近添加了更多安全信息,包括Avoiding SQL injection,与您的示例完全相关.)
> *表达式中的所有位置参数都是由Django编译的,即Value(字符串)被移动到参数,它们被数据库驱动程序正确转义.
>其他字符串被解释为字段名称F(名称),然后以右table_name为前缀.别名点,最终添加到该表的连接,并且名称由quote_name()函数处理.
>问题是1.11中的文档仍然很简单,诱人的参数** extra和** extra_context被模糊地记录下来.它们只能用于从不“编译”的简单参数,并且永远不会通过SQL参数.带有安全字符但没有撇号,反斜杠或百分比的数字或简单字符串都很好.它不能是字段名称,因为它不是明确的,也不是连接的.对于先前检查的数字和固定字符串(如“ASC”/“DESC”,时区名称和其他值,例如下拉列表),它是安全的.还有一个弱点.必须在服务器端检查下拉列表值.还必须验证数字是否为数字,而不是像“2”这样的数字字符串,因为所有数据库函数都默认接受省略的数字字符串而不是数字.如果从my_app.my_table传递错误的“数字”为“0”; rogue_sql; – ‘然后注射结束了.请注意,在这种情况下,欺诈字符串不包含任何非常禁止的字符.必须专门检查用户提供的数字,或者必须通过位置表达式传递值.
>可以安全地指定Func类的函数名和arg_joiner字符串属性,或者指定Func()调用的相同函数和arg_joiner参数.模板参数永远不应该在括号内的替换参数表达式中包含撇号:(%(表达式)s),因为如果需要,数据库驱动程序会添加撇号,但是附加的撇号会导致它通常无法正常工作,但有时它会可能会被忽视,这将导致another security issue.

与安全无关的注释

>许多带有一个参数的简单内置函数看起来不尽可能简单,因为它们是从Func的多用途后代派生的.例如,Length是一个也可以用作查找Transform的函数.

class Length(Transform):
    """Return the number of characters in the expression."""
    function = 'LENGTH'
    output_field = fields.IntegerField()  # sometimes specified the type
    # lookup_name = 'length'  # useful for lookup not for Func usage

查找转换将相同的函数应用于查找的左侧和右侧.

# I'm searching people with usernames longer than mine 
qs = User.objects.filter(username__length__gt=my_username)

>如果没有覆盖,可以在Func .__ init __()中指定可以在Func.as_sql(…,function = …,template = …,arg_joiner = …)中指定的相同关键字参数.自定义as_sql()或它们可以设置为Func的自定义后代类的属性.
>许多SQL数据库函数都有一个冗长的语法,如POSITION(substring IN string),因为如果不支持命名参数,如POSITION($1 IN $2)和简短变体STRPOS(string,substring)(por postgres)或INSTR(它),它会简化可读性.字符串,子字符串)(对于其他数据库),由Func()更容易实现,可读性由Python包装器使用__init __(表达式,子字符串)修复.
>同样非常复杂的函数可以通过更多嵌套函数和简单参数安全方式的组合来实现:Case(When(field_name = lookup_value,then = Value(value)),When(…),… default = Value (值)).

标签:python,django,django-queryset,django-annotate
来源: https://codeday.me/bug/20191006/1861250.html